Serenity Operating System
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}