Serenity Operating System
at master 155 lines 6.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 "RemoteObject.h" 8#include "RemoteObjectGraphModel.h" 9#include "RemoteObjectPropertyModel.h" 10#include "RemoteProcess.h" 11#include <AK/URL.h> 12#include <LibCore/System.h> 13#include <LibDesktop/Launcher.h> 14#include <LibGUI/Application.h> 15#include <LibGUI/BoxLayout.h> 16#include <LibGUI/Clipboard.h> 17#include <LibGUI/Menu.h> 18#include <LibGUI/Menubar.h> 19#include <LibGUI/MessageBox.h> 20#include <LibGUI/ModelEditingDelegate.h> 21#include <LibGUI/ProcessChooser.h> 22#include <LibGUI/Splitter.h> 23#include <LibGUI/TreeView.h> 24#include <LibGUI/Window.h> 25#include <LibMain/Main.h> 26#include <stdio.h> 27#include <unistd.h> 28 29using namespace Inspector; 30 31[[noreturn]] static void print_usage_and_exit() 32{ 33 outln("usage: Inspector <pid>"); 34 exit(0); 35} 36 37ErrorOr<int> serenity_main(Main::Arguments arguments) 38{ 39 TRY(Core::System::pledge("stdio recvfd sendfd rpath unix")); 40 TRY(Core::System::unveil("/res", "r")); 41 TRY(Core::System::unveil("/bin", "r")); 42 TRY(Core::System::unveil("/tmp", "rwc")); 43 TRY(Core::System::unveil("/sys/kernel/processes", "r")); 44 TRY(Core::System::unveil("/etc/passwd", "r")); 45 TRY(Core::System::unveil(nullptr, nullptr)); 46 47 bool gui_mode = arguments.argc != 2; 48 pid_t pid; 49 50 auto app = TRY(GUI::Application::try_create(arguments)); 51 auto app_icon = TRY(GUI::Icon::try_create_default_icon("app-inspector"sv)); 52 if (gui_mode) { 53 choose_pid: 54 auto process_chooser = TRY(GUI::ProcessChooser::try_create("Inspector"sv, "Inspect"_short_string, app_icon.bitmap_for_size(16))); 55 if (process_chooser->exec() == GUI::Dialog::ExecResult::Cancel) 56 return 0; 57 pid = process_chooser->pid(); 58 } else { 59 auto pid_opt = DeprecatedString(arguments.strings[1]).to_int(); 60 if (!pid_opt.has_value()) 61 print_usage_and_exit(); 62 pid = pid_opt.value(); 63 } 64 65 auto window = TRY(GUI::Window::try_create()); 66 67 if (pid == getpid()) { 68 GUI::MessageBox::show(window, "Cannot inspect Inspector itself!"sv, "Error"sv, GUI::MessageBox::Type::Error); 69 if (gui_mode) 70 goto choose_pid; 71 else 72 return 1; 73 } 74 75 RemoteProcess remote_process(pid); 76 if (!remote_process.is_inspectable()) { 77 GUI::MessageBox::show(window, DeprecatedString::formatted("Process pid={} is not inspectable", remote_process.pid()), "Error"sv, GUI::MessageBox::Type::Error); 78 if (gui_mode) { 79 goto choose_pid; 80 } else { 81 return 1; 82 } 83 } 84 85 TRY(Desktop::Launcher::add_allowed_handler_with_only_specific_urls("/bin/Help", { URL::create_with_file_scheme("/usr/share/man/man1/Inspector.md") })); 86 TRY(Desktop::Launcher::seal_allowlist()); 87 88 window->set_title("Inspector"); 89 window->resize(685, 500); 90 window->set_icon(app_icon.bitmap_for_size(16)); 91 92 auto& file_menu = window->add_menu("&File"); 93 file_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); })); 94 95 auto& help_menu = window->add_menu("&Help"); 96 help_menu.add_action(GUI::CommonActions::make_command_palette_action(window)); 97 help_menu.add_action(GUI::CommonActions::make_help_action([](auto&) { 98 Desktop::Launcher::open(URL::create_with_file_scheme("/usr/share/man/man1/Inspector.md"), "/bin/Help"); 99 })); 100 help_menu.add_action(GUI::CommonActions::make_about_action("Inspector", app_icon, window)); 101 102 auto widget = TRY(window->set_main_widget<GUI::Widget>()); 103 widget->set_fill_with_background_color(true); 104 widget->set_layout<GUI::VerticalBoxLayout>(); 105 106 auto& splitter = widget->add<GUI::HorizontalSplitter>(); 107 108 remote_process.on_update = [&] { 109 if (!remote_process.process_name().is_null()) 110 window->set_title(DeprecatedString::formatted("{} ({}) - Inspector", remote_process.process_name(), remote_process.pid())); 111 }; 112 113 auto& tree_view = splitter.add<GUI::TreeView>(); 114 tree_view.set_model(remote_process.object_graph_model()); 115 tree_view.set_activates_on_selection(true); 116 tree_view.set_preferred_width(286); 117 118 auto& properties_tree_view = splitter.add<GUI::TreeView>(); 119 properties_tree_view.set_should_fill_selected_rows(true); 120 properties_tree_view.set_editable(true); 121 properties_tree_view.aid_create_editing_delegate = [](auto&) { 122 return make<GUI::StringModelEditingDelegate>(); 123 }; 124 125 tree_view.on_activation = [&](auto& index) { 126 auto* remote_object = static_cast<RemoteObject*>(index.internal_data()); 127 properties_tree_view.set_model(remote_object->property_model()); 128 remote_process.set_inspected_object(remote_object->address); 129 }; 130 131 auto properties_tree_view_context_menu = TRY(GUI::Menu::try_create("Properties Tree View")); 132 133 auto copy_bitmap = Gfx::Bitmap::load_from_file("/res/icons/16x16/edit-copy.png"sv).release_value_but_fixme_should_propagate_errors(); 134 auto copy_property_name_action = GUI::Action::create("Copy Property Name", copy_bitmap, [&](auto&) { 135 GUI::Clipboard::the().set_plain_text(properties_tree_view.selection().first().data().to_deprecated_string()); 136 }); 137 auto copy_property_value_action = GUI::Action::create("Copy Property Value", copy_bitmap, [&](auto&) { 138 GUI::Clipboard::the().set_plain_text(properties_tree_view.selection().first().sibling_at_column(1).data().to_deprecated_string()); 139 }); 140 141 properties_tree_view_context_menu->add_action(copy_property_name_action); 142 properties_tree_view_context_menu->add_action(copy_property_value_action); 143 144 properties_tree_view.on_context_menu_request = [&](const GUI::ModelIndex& index, const GUI::ContextMenuEvent& event) { 145 if (index.is_valid()) { 146 properties_tree_view_context_menu->popup(event.screen_position()); 147 } 148 }; 149 150 window->show(); 151 remote_process.update(); 152 153 TRY(Core::System::pledge("stdio recvfd sendfd rpath")); 154 return app->exec(); 155}