diff --git a/Base/res/icons/16x16/app-run.png b/Base/res/icons/16x16/app-run.png new file mode 100644 index 00000000000..9cd6ea6ca6a Binary files /dev/null and b/Base/res/icons/16x16/app-run.png differ diff --git a/Base/res/icons/32x32/app-run.png b/Base/res/icons/32x32/app-run.png new file mode 100644 index 00000000000..a2384dd2e1d Binary files /dev/null and b/Base/res/icons/32x32/app-run.png differ diff --git a/Userland/Applications/CMakeLists.txt b/Userland/Applications/CMakeLists.txt index 41803202e65..c89932e2929 100644 --- a/Userland/Applications/CMakeLists.txt +++ b/Userland/Applications/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(MouseSettings) add_subdirectory(Piano) add_subdirectory(PixelPaint) add_subdirectory(QuickShow) +add_subdirectory(Run) add_subdirectory(SoundPlayer) add_subdirectory(SpaceAnalyzer) add_subdirectory(Spreadsheet) diff --git a/Userland/Applications/Run/CMakeLists.txt b/Userland/Applications/Run/CMakeLists.txt new file mode 100644 index 00000000000..4513f91890d --- /dev/null +++ b/Userland/Applications/Run/CMakeLists.txt @@ -0,0 +1,11 @@ +compile_gml(Run.gml RunGML.h run_gml) + +set(SOURCES + main.cpp + RunWindow.cpp + RunWindow.h + RunGML.h +) + +serenity_app(Run ICON app-run) +target_link_libraries(Run LibCore LibDesktop LibGUI) diff --git a/Userland/Applications/Run/Run.gml b/Userland/Applications/Run/Run.gml new file mode 100644 index 00000000000..798293103fa --- /dev/null +++ b/Userland/Applications/Run/Run.gml @@ -0,0 +1,65 @@ +@GUI::Widget { + fill_with_background_color: true + + layout: @GUI::VerticalBoxLayout { + margins: [10, 10, 10, 10] + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 10 + } + + @GUI::ImageWidget { + name: "icon" + } + + @GUI::Label { + name: "info" + text: "Type the name of a program, folder, document,\\nor website, and SerenityOS will open it for you." + text_alignment: "CenterLeft" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + } + + @GUI::Label { + text: "Open:" + fixed_width: 50 + text_alignment: "CenterLeft" + } + + @GUI::TextBox { + name: "path" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + } + + // HACK: using an empty widget as a spacer + @GUI::Widget { + } + + @GUI::Button { + name: "ok_button" + text: "OK" + fixed_width: 75 + } + + @GUI::Button { + name: "cancel_button" + text: "Cancel" + fixed_width: 75 + } + + @GUI::Button { + name: "browse_button" + text: "Browse..." + fixed_width: 75 + } + } +} diff --git a/Userland/Applications/Run/RunWindow.cpp b/Userland/Applications/Run/RunWindow.cpp new file mode 100644 index 00000000000..8980f304e78 --- /dev/null +++ b/Userland/Applications/Run/RunWindow.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2021, Nick Vella + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "RunWindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +RunWindow::RunWindow() +{ + auto app_icon = GUI::Icon::default_icon("app-run"); + + set_title("Run"); + set_icon(app_icon.bitmap_for_size(16)); + resize(345, 140); + set_resizable(false); + set_minimizable(false); + + auto& main_widget = set_main_widget(); + main_widget.load_from_gml(run_gml); + + m_icon_image_widget = *main_widget.find_descendant_of_type_named("icon"); + m_icon_image_widget->set_bitmap(app_icon.bitmap_for_size(32)); + + m_path_text_box = *main_widget.find_descendant_of_type_named("path"); + m_path_text_box->on_return_pressed = [this] { + m_ok_button->click(); + }; + + m_ok_button = *main_widget.find_descendant_of_type_named("ok_button"); + m_ok_button->on_click = [this](auto) { + do_run(); + }; + + m_cancel_button = *main_widget.find_descendant_of_type_named("cancel_button"); + m_cancel_button->on_click = [this](auto) { + close(); + }; + + m_browse_button = *find_descendant_of_type_named("browse_button"); + m_browse_button->on_click = [this](auto) { + Optional path = GUI::FilePicker::get_open_filepath(this); + if (path.has_value()) + m_path_text_box->set_text(path.value().view()); + }; +} + +RunWindow::~RunWindow() +{ +} + +void RunWindow::event(Core::Event& event) +{ + if (event.type() == GUI::Event::KeyUp || event.type() == GUI::Event::KeyDown) { + auto& key_event = static_cast(event); + if (key_event.key() == Key_Escape) { + // Escape key pressed, close dialog + close(); + return; + } + } + + Window::event(event); +} + +void RunWindow::do_run() +{ + auto run_input = m_path_text_box->text(); + + hide(); + + if (run_via_launch(run_input) || run_as_command(run_input)) { + close(); + return; + } + + GUI::MessageBox::show_error(this, "Failed to run. Please check your command, path, or address, and try again."); + + show(); +} + +bool RunWindow::run_as_command(const String& run_input) +{ + pid_t child_pid; + const char* shell_executable = "/bin/Shell"; // TODO query and use the user's preferred shell. + const char* argv[] = { shell_executable, "-c", run_input.characters(), nullptr }; + + if ((errno = posix_spawn(&child_pid, shell_executable, nullptr, nullptr, const_cast(argv), environ))) { + perror("posix_spawn"); + return false; + } + + // Command spawned in child shell. Hide and wait for exit code. + int status; + if (waitpid(child_pid, &status, 0) < 0) + return false; + + int child_error = WEXITSTATUS(status); + dbgln("Child shell exited with code {}", child_error); + + // 127 is typically the shell indicating command not found. 126 for all other errors. + if (child_error == 126 || child_error == 127) { + return false; + } + + dbgln("Ran via command shell."); + + return true; +} + +bool RunWindow::run_via_launch(const String& run_input) +{ + auto url = URL::create_with_url_or_path(run_input); + + if (url.protocol() == "file") { + auto real_path = Core::File::real_path_for(url.path()); + if (real_path.is_null()) { + // errno *should* be preserved from Core::File::real_path_for(). + warnln("Failed to launch '{}': {}", url.path(), strerror(errno)); + return false; + } + url = URL::create_with_url_or_path(real_path); + } + + if (!Desktop::Launcher::open(url)) { + warnln("Failed to launch '{}'", url); + return false; + } + + dbgln("Ran via URL launch."); + + return true; +} diff --git a/Userland/Applications/Run/RunWindow.h b/Userland/Applications/Run/RunWindow.h new file mode 100644 index 00000000000..f8cd19ea618 --- /dev/null +++ b/Userland/Applications/Run/RunWindow.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021, Nick Vella + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include +#include + +class RunWindow final : public GUI::Window { + C_OBJECT(RunWindow) +public: + virtual ~RunWindow() override; + + virtual void event(Core::Event&) override; + +private: + RunWindow(); + + void do_run(); + bool run_as_command(const String& run_input); + bool run_via_launch(const String& run_input); + + RefPtr m_icon_image_widget; + RefPtr m_ok_button; + RefPtr m_cancel_button; + RefPtr m_browse_button; + RefPtr m_path_text_box; +}; diff --git a/Userland/Applications/Run/main.cpp b/Userland/Applications/Run/main.cpp new file mode 100644 index 00000000000..a91608d7461 --- /dev/null +++ b/Userland/Applications/Run/main.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021, Nick Vella + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include "RunWindow.h" + +int main(int argc, char** argv) +{ + if (pledge("stdio sendfd shared_buffer accept cpath rpath unix fattr proc exec", nullptr) < 0) { + perror("pledge"); + return 1; + } + + auto app = GUI::Application::construct(argc, argv); + + if (pledge("stdio sendfd shared_buffer accept rpath unix proc exec", nullptr) < 0) { + perror("pledge"); + return 1; + } + + auto window = RunWindow::construct(); + + window->show(); + + return app->exec(); +}