Serenity Operating System
1/*
2 * Copyright (c) 2020, the SerenityOS developers.
3 * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <LibCore/ArgsParser.h>
9#include <LibCore/File.h>
10#include <LibCore/System.h>
11#include <LibGUI/Action.h>
12#include <LibGUI/Application.h>
13#include <LibGUI/ImageWidget.h>
14#include <LibGUI/Menu.h>
15#include <LibGUI/Notification.h>
16#include <LibGUI/Process.h>
17#include <LibGUI/Window.h>
18#include <LibGfx/Bitmap.h>
19#include <LibMain/Main.h>
20
21class NetworkWidget final : public GUI::ImageWidget {
22 C_OBJECT_ABSTRACT(NetworkWidget)
23
24public:
25 static ErrorOr<NonnullRefPtr<NetworkWidget>> try_create(bool notifications)
26 {
27 NonnullRefPtr<Gfx::Bitmap> connected_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/network.png"sv));
28 NonnullRefPtr<Gfx::Bitmap> disconnected_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/network-disconnected.png"sv));
29 return adopt_nonnull_ref_or_enomem(new (nothrow) NetworkWidget(notifications, move(connected_icon), move(disconnected_icon)));
30 }
31
32private:
33 NetworkWidget(bool notifications, NonnullRefPtr<Gfx::Bitmap> connected_icon, NonnullRefPtr<Gfx::Bitmap> disconnected_icon)
34 : m_connected_icon(move(connected_icon))
35 , m_disconnected_icon(move(disconnected_icon))
36 {
37 m_notifications = notifications;
38 update_widget();
39 start_timer(5000);
40 }
41
42 virtual void timer_event(Core::TimerEvent&) override
43 {
44 update_widget();
45 }
46
47 virtual void mousedown_event(GUI::MouseEvent& event) override
48 {
49 if (event.button() != GUI::MouseButton::Primary)
50 return;
51 GUI::Process::spawn_or_show_error(window(), "/bin/SystemMonitor"sv, Array { "-t", "network" });
52 }
53
54 void update_widget()
55 {
56 auto adapter_info = get_adapter_info();
57
58 if (adapter_info == "") {
59 set_connected(false);
60 m_adapter_info = "No network adapters";
61 } else {
62 m_adapter_info = adapter_info;
63 }
64
65 set_tooltip(m_adapter_info);
66
67 if (m_connected)
68 NetworkWidget::set_bitmap(m_connected_icon);
69 else
70 NetworkWidget::set_bitmap(m_disconnected_icon);
71
72 update();
73 }
74
75 void notify_on_connect()
76 {
77 if (!m_notifications)
78 return;
79 auto notification = GUI::Notification::construct();
80 notification->set_title("Network");
81 notification->set_icon(m_connected_icon);
82 notification->set_text("Network connected");
83 notification->show();
84 }
85
86 void notify_on_disconnect()
87 {
88 if (!m_notifications)
89 return;
90 auto notification = GUI::Notification::construct();
91 notification->set_title("Network");
92 notification->set_icon(m_disconnected_icon);
93 notification->set_text("Network disconnected");
94 notification->show();
95 }
96
97 void set_connected(bool connected)
98 {
99 if (m_connected != connected) {
100 connected ? notify_on_connect() : notify_on_disconnect();
101 }
102
103 m_connected = connected;
104 }
105
106 DeprecatedString get_adapter_info()
107 {
108 StringBuilder adapter_info;
109
110 auto file_or_error = Core::File::open("/sys/kernel/net/adapters"sv, Core::File::OpenMode::Read);
111 if (file_or_error.is_error()) {
112 dbgln("Error: Could not open /sys/kernel/net/adapters: {}", file_or_error.error());
113 return "";
114 }
115
116 auto file_contents_or_error = file_or_error.value()->read_until_eof();
117 if (file_contents_or_error.is_error()) {
118 dbgln("Error: Could not read /sys/kernel/net/adapters: {}", file_contents_or_error.error());
119 return "";
120 }
121
122 auto json = JsonValue::from_string(file_contents_or_error.value());
123
124 if (json.is_error())
125 return adapter_info.to_deprecated_string();
126
127 int connected_adapters = 0;
128 json.value().as_array().for_each([&adapter_info, &connected_adapters](auto& value) {
129 auto& if_object = value.as_object();
130 auto ip_address = if_object.get_deprecated_string("ipv4_address"sv).value_or("no IP");
131 auto ifname = if_object.get_deprecated_string("name"sv).value();
132 auto link_up = if_object.get_bool("link_up"sv).value();
133 auto link_speed = if_object.get_i32("link_speed"sv).value();
134
135 if (ifname == "loop")
136 return;
137
138 if (ip_address != "null")
139 connected_adapters++;
140
141 if (!adapter_info.is_empty())
142 adapter_info.append('\n');
143
144 adapter_info.appendff("{}: {} ", ifname, ip_address);
145 if (!link_up)
146 adapter_info.appendff("(down)");
147 else
148 adapter_info.appendff("({} Mb/s)", link_speed);
149 });
150
151 // show connected icon so long as at least one adapter is connected
152 connected_adapters ? set_connected(true) : set_connected(false);
153
154 return adapter_info.to_deprecated_string();
155 }
156
157 DeprecatedString m_adapter_info;
158 bool m_connected = false;
159 bool m_notifications = true;
160 NonnullRefPtr<Gfx::Bitmap> m_connected_icon;
161 NonnullRefPtr<Gfx::Bitmap> m_disconnected_icon;
162};
163
164ErrorOr<int> serenity_main(Main::Arguments arguments)
165{
166 TRY(Core::System::pledge("stdio recvfd sendfd rpath unix proc exec"));
167 auto app = TRY(GUI::Application::try_create(arguments));
168
169 TRY(Core::System::unveil("/tmp/session/%sid/portal/notify", "rw"));
170 TRY(Core::System::unveil("/res", "r"));
171 TRY(Core::System::unveil("/sys/kernel/net/adapters", "r"));
172 TRY(Core::System::unveil("/bin/SystemMonitor", "x"));
173 TRY(Core::System::unveil(nullptr, nullptr));
174
175 bool display_notifications = false;
176 StringView name;
177 Core::ArgsParser args_parser;
178 args_parser.add_option(display_notifications, "Display notifications", "display-notifications", 'd');
179 args_parser.add_option(name, "Applet name used by WindowServer.ini to set the applet order", "name", 'n', "name");
180 args_parser.parse(arguments);
181
182 if (name.is_empty())
183 name = "Network"sv;
184
185 auto window = TRY(GUI::Window::try_create());
186 window->set_title(name);
187 window->set_window_type(GUI::WindowType::Applet);
188 window->set_has_alpha_channel(true);
189 window->resize(16, 16);
190 auto icon = TRY(window->set_main_widget<NetworkWidget>(display_notifications));
191 icon->load_from_file("/res/icons/16x16/network.png"sv);
192 window->resize(16, 16);
193 window->show();
194
195 return app->exec();
196}