Serenity Operating System
at master 227 lines 7.0 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "Editor.h" 8#include "HackStudio.h" 9#include "HackStudioWidget.h" 10#include "Project.h" 11#include <AK/StringBuilder.h> 12#include <LibConfig/Client.h> 13#include <LibCore/ArgsParser.h> 14#include <LibCore/DeprecatedFile.h> 15#include <LibCore/System.h> 16#include <LibGUI/Application.h> 17#include <LibGUI/Menubar.h> 18#include <LibGUI/MessageBox.h> 19#include <LibGUI/Notification.h> 20#include <LibGUI/Window.h> 21#include <LibMain/Main.h> 22#include <fcntl.h> 23#include <spawn.h> 24#include <stdio.h> 25#include <sys/types.h> 26#include <sys/wait.h> 27#include <unistd.h> 28 29using namespace HackStudio; 30 31static WeakPtr<HackStudioWidget> s_hack_studio_widget; 32 33static bool make_is_available(); 34static ErrorOr<void> notify_make_not_available(); 35static void update_path_environment_variable(); 36static Optional<DeprecatedString> last_opened_project_path(); 37static ErrorOr<NonnullRefPtr<HackStudioWidget>> create_hack_studio_widget(bool mode_coredump, DeprecatedString const& path, pid_t pid_to_debug); 38 39ErrorOr<int> serenity_main(Main::Arguments arguments) 40{ 41 TRY(Core::System::pledge("stdio recvfd sendfd tty rpath cpath wpath proc exec unix fattr thread ptrace")); 42 43 auto app = TRY(GUI::Application::try_create(arguments)); 44 Config::pledge_domains({ "HackStudio", "Terminal" }); 45 46 auto window = GUI::Window::construct(); 47 window->resize(840, 600); 48 auto icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-hack-studio.png"sv)); 49 window->set_icon(icon); 50 51 update_path_environment_variable(); 52 53 if (!make_is_available()) { 54 TRY(notify_make_not_available()); 55 } 56 57 StringView path_argument; 58 bool mode_coredump = false; 59 pid_t pid_to_debug = -1; 60 Core::ArgsParser args_parser; 61 args_parser.add_positional_argument(path_argument, "Path to a workspace or a file", "path", Core::ArgsParser::Required::No); 62 args_parser.add_option(mode_coredump, "Debug a coredump in HackStudio", "coredump", 'c'); 63 args_parser.add_option(pid_to_debug, "Attach debugger to running process", "pid", 'p', "PID"); 64 args_parser.parse(arguments); 65 66 auto absolute_path_argument = Core::DeprecatedFile::real_path_for(path_argument); 67 auto hack_studio_widget = TRY(create_hack_studio_widget(mode_coredump, absolute_path_argument, pid_to_debug)); 68 window->set_main_widget(hack_studio_widget); 69 s_hack_studio_widget = hack_studio_widget; 70 71 window->set_title(DeprecatedString::formatted("{} - Hack Studio", hack_studio_widget->project().name())); 72 73 TRY(hack_studio_widget->initialize_menubar(*window)); 74 75 window->on_close_request = [&]() -> GUI::Window::CloseRequestDecision { 76 hack_studio_widget->locator().close(); 77 if (hack_studio_widget->warn_unsaved_changes("There are unsaved changes, do you want to save before exiting?") == HackStudioWidget::ContinueDecision::Yes) 78 return GUI::Window::CloseRequestDecision::Close; 79 return GUI::Window::CloseRequestDecision::StayOpen; 80 }; 81 82 window->show(); 83 hack_studio_widget->update_actions(); 84 85 if (mode_coredump) 86 hack_studio_widget->open_coredump(absolute_path_argument); 87 88 if (pid_to_debug != -1) 89 hack_studio_widget->debug_process(pid_to_debug); 90 91 return app->exec(); 92} 93 94static bool make_is_available() 95{ 96 pid_t pid; 97 char const* argv[] = { "make", "--version", nullptr }; 98 posix_spawn_file_actions_t action; 99 posix_spawn_file_actions_init(&action); 100 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); 101 102 if ((errno = posix_spawnp(&pid, "make", &action, nullptr, const_cast<char**>(argv), environ))) { 103 perror("posix_spawn"); 104 return false; 105 } 106 int wstatus; 107 waitpid(pid, &wstatus, 0); 108 posix_spawn_file_actions_destroy(&action); 109 return WEXITSTATUS(wstatus) == 0; 110} 111 112static ErrorOr<void> notify_make_not_available() 113{ 114 auto notification = GUI::Notification::construct(); 115 auto icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"sv)); 116 notification->set_icon(icon); 117 notification->set_title("'make' Not Available"); 118 notification->set_text("You probably want to install the binutils, gcc, and make ports from the root of the Serenity repository"); 119 notification->show(); 120 return {}; 121} 122 123static void update_path_environment_variable() 124{ 125 StringBuilder path; 126 127 auto const* path_env_ptr = getenv("PATH"); 128 if (path_env_ptr != NULL) 129 path.append({ path_env_ptr, strlen(path_env_ptr) }); 130 131 if (path.length()) 132 path.append(':'); 133 path.append(DEFAULT_PATH_SV); 134 setenv("PATH", path.to_deprecated_string().characters(), true); 135} 136 137static Optional<DeprecatedString> last_opened_project_path() 138{ 139 auto projects = HackStudioWidget::read_recent_projects(); 140 if (projects.size() == 0) 141 return {}; 142 143 if (!Core::DeprecatedFile::exists(projects[0])) 144 return {}; 145 146 return { projects[0] }; 147} 148 149namespace HackStudio { 150 151GUI::TextEditor& current_editor() 152{ 153 return s_hack_studio_widget->current_editor(); 154} 155 156void open_file(DeprecatedString const& filename) 157{ 158 s_hack_studio_widget->open_file(filename); 159} 160 161void open_file(DeprecatedString const& filename, size_t line, size_t column) 162{ 163 s_hack_studio_widget->open_file(filename, line, column); 164} 165 166RefPtr<EditorWrapper> current_editor_wrapper() 167{ 168 if (!s_hack_studio_widget) 169 return nullptr; 170 return s_hack_studio_widget->current_editor_wrapper(); 171} 172 173Project& project() 174{ 175 return s_hack_studio_widget->project(); 176} 177 178DeprecatedString currently_open_file() 179{ 180 if (!s_hack_studio_widget) 181 return {}; 182 return s_hack_studio_widget->active_file(); 183} 184 185void set_current_editor_wrapper(RefPtr<EditorWrapper> wrapper) 186{ 187 s_hack_studio_widget->set_current_editor_wrapper(wrapper); 188} 189 190void update_editor_window_title() 191{ 192 s_hack_studio_widget->update_current_editor_title(); 193 s_hack_studio_widget->update_window_title(); 194} 195 196Locator& locator() 197{ 198 return s_hack_studio_widget->locator(); 199} 200 201void for_each_open_file(Function<void(ProjectFile const&)> func) 202{ 203 s_hack_studio_widget->for_each_open_file(move(func)); 204} 205 206bool semantic_syntax_highlighting_is_enabled() 207{ 208 return s_hack_studio_widget->semantic_syntax_highlighting_is_enabled(); 209} 210 211} 212 213static ErrorOr<NonnullRefPtr<HackStudioWidget>> create_hack_studio_widget(bool mode_coredump, DeprecatedString const& absolute_path_argument, pid_t pid_to_debug) 214{ 215 auto project_path = Core::DeprecatedFile::real_path_for("."); 216 if (!mode_coredump) { 217 if (!absolute_path_argument.is_null()) 218 project_path = absolute_path_argument; 219 else if (auto last_path = last_opened_project_path(); last_path.has_value()) 220 project_path = last_path.release_value(); 221 } 222 223 if (pid_to_debug != -1) 224 project_path = "/usr/src/serenity"; 225 226 return HackStudioWidget::create(project_path); 227}