mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-24 21:45:20 +00:00
Applications: Add a new NetworkSettings application
This commit is contained in:
parent
df7b7cbfa4
commit
7dd3c5c981
Notes:
sideshowbarker
2024-07-17 09:34:36 +09:00
Author: https://github.com/sppmacd Commit: https://github.com/SerenityOS/serenity/commit/7dd3c5c981 Pull-request: https://github.com/SerenityOS/serenity/pull/13259 Reviewed-by: https://github.com/AtkinsSJ ✅ Reviewed-by: https://github.com/IdanHo
9 changed files with 462 additions and 1 deletions
83
Base/home/anon/NetworkSettings.gml
Normal file
83
Base/home/anon/NetworkSettings.gml
Normal file
|
@ -0,0 +1,83 @@
|
|||
@GUI::Frame {
|
||||
fill_with_background_color: true
|
||||
layout: @GUI::VerticalBoxLayout {
|
||||
margins: [10]
|
||||
spacing: 5
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
spacing: 20
|
||||
}
|
||||
fixed_height: 40
|
||||
|
||||
@GUI::Label {
|
||||
fixed_width: 32
|
||||
fixed_height: 32
|
||||
icon: "/res/icons/32x32/network.png"
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
text: "Select adapter:"
|
||||
fixed_width: 100
|
||||
text_alignment: "CenterLeft"
|
||||
}
|
||||
|
||||
@GUI::ComboBox {
|
||||
model_only: true
|
||||
name: "adapters_combobox"
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::GroupBox {
|
||||
title: "Network"
|
||||
shrink_to_fit: true
|
||||
layout: @GUI::VerticalBoxLayout {
|
||||
margins: [10]
|
||||
}
|
||||
|
||||
@GUI::CheckBox {}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {}
|
||||
fixed_height: 30
|
||||
|
||||
@GUI::Label {
|
||||
text: "IP Address:"
|
||||
fixed_width: 100
|
||||
text_alignment: "CenterLeft"
|
||||
}
|
||||
|
||||
@GUI::TextBox {
|
||||
name: "ip_address_textbox"
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
text: "/"
|
||||
fixed_width: 10
|
||||
}
|
||||
|
||||
@GUI::SpinBox {
|
||||
name: "cidr_spinbox"
|
||||
fixed_width: 50
|
||||
min: 1
|
||||
max: 32
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {}
|
||||
fixed_height: 30
|
||||
|
||||
@GUI::Label {
|
||||
text: "Default gateway:"
|
||||
fixed_width: 100
|
||||
text_alignment: "CenterLeft"
|
||||
}
|
||||
|
||||
@GUI::TextBox {
|
||||
name: "default_gateway_textbox"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
Base/res/apps/NetworkSettings.af
Normal file
5
Base/res/apps/NetworkSettings.af
Normal file
|
@ -0,0 +1,5 @@
|
|||
[App]
|
||||
Name=Network Settings
|
||||
Executable=/bin/NetworkSettings
|
||||
Category=Settings
|
||||
Description=Configure network connections
|
|
@ -22,6 +22,7 @@ add_subdirectory(Magnifier)
|
|||
add_subdirectory(Mail)
|
||||
add_subdirectory(MailSettings)
|
||||
add_subdirectory(MouseSettings)
|
||||
add_subdirectory(NetworkSettings)
|
||||
add_subdirectory(PDFViewer)
|
||||
add_subdirectory(Piano)
|
||||
add_subdirectory(PixelPaint)
|
||||
|
|
17
Userland/Applications/NetworkSettings/CMakeLists.txt
Normal file
17
Userland/Applications/NetworkSettings/CMakeLists.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
serenity_component(
|
||||
NetworkSettings
|
||||
REQUIRED
|
||||
TARGETS NetworkSettings
|
||||
)
|
||||
|
||||
compile_gml(NetworkSettings.gml NetworkSettingsGML.h network_settings_gml)
|
||||
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
NetworkSettingsGML.h
|
||||
NetworkSettingsWidget.cpp
|
||||
NetworkSettingsWidget.h
|
||||
)
|
||||
|
||||
serenity_app(NetworkSettings ICON network)
|
||||
target_link_libraries(NetworkSettings LibGUI LibMain)
|
91
Userland/Applications/NetworkSettings/NetworkSettings.gml
Normal file
91
Userland/Applications/NetworkSettings/NetworkSettings.gml
Normal file
|
@ -0,0 +1,91 @@
|
|||
@GUI::Frame {
|
||||
fill_with_background_color: true
|
||||
layout: @GUI::VerticalBoxLayout {
|
||||
margins: [10]
|
||||
spacing: 5
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
spacing: 20
|
||||
}
|
||||
fixed_height: 40
|
||||
|
||||
@GUI::Label {
|
||||
fixed_width: 32
|
||||
fixed_height: 32
|
||||
icon: "/res/icons/32x32/network.png"
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
text: "Select adapter:"
|
||||
fixed_width: 100
|
||||
text_alignment: "CenterLeft"
|
||||
}
|
||||
|
||||
@GUI::ComboBox {
|
||||
model_only: true
|
||||
name: "adapters_combobox"
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::GroupBox {
|
||||
title: "Network"
|
||||
shrink_to_fit: true
|
||||
layout: @GUI::VerticalBoxLayout {
|
||||
margins: [10]
|
||||
}
|
||||
|
||||
@GUI::CheckBox {
|
||||
text: "Enabled"
|
||||
name: "enabled_checkbox"
|
||||
}
|
||||
|
||||
@GUI::CheckBox {
|
||||
text: "Obtain settings automatically (using DHCP)"
|
||||
name: "dhcp_checkbox"
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {}
|
||||
preferred_height: 30
|
||||
|
||||
@GUI::Label {
|
||||
text: "IP Address:"
|
||||
fixed_width: 100
|
||||
text_alignment: "CenterLeft"
|
||||
}
|
||||
|
||||
@GUI::TextBox {
|
||||
name: "ip_address_textbox"
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
text: "/"
|
||||
fixed_width: 10
|
||||
}
|
||||
|
||||
@GUI::SpinBox {
|
||||
name: "cidr_spinbox"
|
||||
fixed_width: 50
|
||||
min: 1
|
||||
max: 32
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {}
|
||||
preferred_height: 30
|
||||
|
||||
@GUI::Label {
|
||||
text: "Default gateway:"
|
||||
fixed_width: 100
|
||||
text_alignment: "CenterLeft"
|
||||
}
|
||||
|
||||
@GUI::TextBox {
|
||||
name: "default_gateway_textbox"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
169
Userland/Applications/NetworkSettings/NetworkSettingsWidget.cpp
Normal file
169
Userland/Applications/NetworkSettings/NetworkSettingsWidget.cpp
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Maciej <sppmacd@pm.me>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "NetworkSettingsWidget.h"
|
||||
|
||||
#include <AK/IPv4Address.h>
|
||||
#include <AK/JsonParser.h>
|
||||
#include <AK/String.h>
|
||||
#include <Applications/NetworkSettings/NetworkSettingsGML.h>
|
||||
#include <LibCore/Command.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibGUI/CheckBox.h>
|
||||
#include <LibGUI/ComboBox.h>
|
||||
#include <LibGUI/ItemListModel.h>
|
||||
#include <LibGUI/MessageBox.h>
|
||||
#include <LibGUI/Process.h>
|
||||
#include <LibGUI/SpinBox.h>
|
||||
#include <LibGUI/TextBox.h>
|
||||
|
||||
namespace NetworkSettings {
|
||||
|
||||
static int netmask_to_cidr(IPv4Address const& address)
|
||||
{
|
||||
auto address_in_host_representation = AK::convert_between_host_and_network_endian(address.to_u32());
|
||||
return 32 - count_trailing_zeroes_safe(address_in_host_representation);
|
||||
}
|
||||
|
||||
NetworkSettingsWidget::NetworkSettingsWidget()
|
||||
{
|
||||
load_from_gml(network_settings_gml);
|
||||
|
||||
m_adapters_combobox = *find_descendant_of_type_named<GUI::ComboBox>("adapters_combobox");
|
||||
m_enabled_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("enabled_checkbox");
|
||||
m_enabled_checkbox->on_checked = [&](bool value) {
|
||||
m_current_adapter_data->enabled = value;
|
||||
on_switch_enabled_or_dhcp();
|
||||
set_modified(true);
|
||||
};
|
||||
m_dhcp_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("dhcp_checkbox");
|
||||
m_dhcp_checkbox->on_checked = [&](bool value) {
|
||||
m_current_adapter_data->dhcp = value;
|
||||
on_switch_enabled_or_dhcp();
|
||||
set_modified(true);
|
||||
};
|
||||
m_ip_address_textbox = *find_descendant_of_type_named<GUI::TextBox>("ip_address_textbox");
|
||||
m_ip_address_textbox->on_change = [&]() {
|
||||
m_current_adapter_data->ip_address = m_ip_address_textbox->text();
|
||||
set_modified(true);
|
||||
};
|
||||
m_cidr_spinbox = *find_descendant_of_type_named<GUI::SpinBox>("cidr_spinbox");
|
||||
m_cidr_spinbox->on_change = [&](int value) {
|
||||
m_current_adapter_data->cidr = value;
|
||||
set_modified(true);
|
||||
};
|
||||
m_default_gateway_textbox = *find_descendant_of_type_named<GUI::TextBox>("default_gateway_textbox");
|
||||
m_default_gateway_textbox->on_change = [&]() {
|
||||
m_current_adapter_data->default_gateway = m_default_gateway_textbox->text();
|
||||
set_modified(true);
|
||||
};
|
||||
|
||||
auto config_file = Core::ConfigFile::open_for_system("Network").release_value_but_fixme_should_propagate_errors();
|
||||
|
||||
auto proc_net_adapters_file = Core::Stream::File::open("/proc/net/adapters", Core::Stream::OpenMode::Read).release_value_but_fixme_should_propagate_errors();
|
||||
auto data = proc_net_adapters_file->read_all().release_value_but_fixme_should_propagate_errors();
|
||||
JsonParser parser(data);
|
||||
JsonValue proc_net_adapters_json = parser.parse().release_value_but_fixme_should_propagate_errors();
|
||||
|
||||
// FIXME: This should be done before creating a window.
|
||||
if (proc_net_adapters_json.as_array().is_empty()) {
|
||||
GUI::MessageBox::show_error(window(), "No network adapters found!");
|
||||
::exit(1);
|
||||
}
|
||||
|
||||
size_t selected_adapter_index = 0;
|
||||
size_t index = 0;
|
||||
proc_net_adapters_json.as_array().for_each([&](auto& value) {
|
||||
auto& if_object = value.as_object();
|
||||
auto adapter_name = if_object.get("name").to_string();
|
||||
if (adapter_name == "loop")
|
||||
return;
|
||||
|
||||
bool adapter_exists_in_config = config_file->has_group(adapter_name);
|
||||
|
||||
bool enabled = config_file->read_bool_entry(adapter_name, "Enabled", true);
|
||||
if (enabled)
|
||||
selected_adapter_index = index;
|
||||
|
||||
NetworkAdapterData adapter_data;
|
||||
adapter_data.enabled = enabled;
|
||||
adapter_data.dhcp = config_file->read_bool_entry(adapter_name, "DHCP", !adapter_exists_in_config);
|
||||
adapter_data.ip_address = config_file->read_entry(adapter_name, "IPv4Address");
|
||||
auto netmask = IPv4Address::from_string(config_file->read_entry(adapter_name, "IPv4Netmask"));
|
||||
adapter_data.cidr = netmask.has_value() ? netmask_to_cidr(*netmask) : 32;
|
||||
adapter_data.default_gateway = config_file->read_entry(adapter_name, "IPv4Gateway");
|
||||
m_network_adapters.set(adapter_name, move(adapter_data));
|
||||
m_adapter_names.append(adapter_name);
|
||||
index++;
|
||||
});
|
||||
|
||||
m_adapters_combobox->set_model(GUI::ItemListModel<String>::create(m_adapter_names));
|
||||
m_adapters_combobox->on_change = [this](String const& text, GUI::ModelIndex const&) {
|
||||
on_switch_adapter(text);
|
||||
};
|
||||
auto const& selected_adapter = selected_adapter_index;
|
||||
dbgln("{} in {}", selected_adapter, m_adapter_names);
|
||||
m_adapters_combobox->set_selected_index(selected_adapter);
|
||||
on_switch_adapter(m_adapter_names[selected_adapter_index]);
|
||||
}
|
||||
|
||||
void NetworkSettingsWidget::on_switch_adapter(String const& adapter)
|
||||
{
|
||||
auto& adapter_data = m_network_adapters.get(adapter).value();
|
||||
m_current_adapter_data = &adapter_data;
|
||||
on_switch_enabled_or_dhcp();
|
||||
|
||||
m_enabled_checkbox->set_checked(adapter_data.enabled, GUI::AllowCallback::No);
|
||||
m_dhcp_checkbox->set_checked(adapter_data.dhcp, GUI::AllowCallback::No);
|
||||
m_ip_address_textbox->set_text(adapter_data.ip_address, GUI::AllowCallback::No);
|
||||
m_cidr_spinbox->set_value(adapter_data.cidr, GUI::AllowCallback::No);
|
||||
m_default_gateway_textbox->set_text(adapter_data.default_gateway, GUI::AllowCallback::No);
|
||||
|
||||
VERIFY(m_current_adapter_data);
|
||||
}
|
||||
|
||||
void NetworkSettingsWidget::on_switch_enabled_or_dhcp()
|
||||
{
|
||||
m_dhcp_checkbox->set_enabled(m_current_adapter_data->enabled);
|
||||
m_ip_address_textbox->set_enabled(m_current_adapter_data->enabled && !m_current_adapter_data->dhcp);
|
||||
m_cidr_spinbox->set_enabled(m_current_adapter_data->enabled && !m_current_adapter_data->dhcp);
|
||||
m_default_gateway_textbox->set_enabled(m_current_adapter_data->enabled && !m_current_adapter_data->dhcp);
|
||||
}
|
||||
|
||||
void NetworkSettingsWidget::apply_settings()
|
||||
{
|
||||
auto config_file = Core::ConfigFile::open_for_system("Network", Core::ConfigFile::AllowWriting::Yes).release_value_but_fixme_should_propagate_errors();
|
||||
bool may_need_to_reboot = false;
|
||||
for (auto const& adapter_data : m_network_adapters) {
|
||||
// FIXME: Setting Enabled=false starts working only after a reboot. Fix this on the NetworkServer side.
|
||||
if (!m_current_adapter_data->enabled)
|
||||
may_need_to_reboot = true;
|
||||
|
||||
auto netmask = IPv4Address::netmask_from_cidr(adapter_data.value.cidr).to_string();
|
||||
config_file->write_bool_entry(adapter_data.key, "Enabled", adapter_data.value.enabled);
|
||||
config_file->write_bool_entry(adapter_data.key, "DHCP", adapter_data.value.dhcp);
|
||||
if (adapter_data.value.enabled && !adapter_data.value.dhcp) {
|
||||
if (!IPv4Address::from_string(adapter_data.value.ip_address).has_value()) {
|
||||
GUI::MessageBox::show_error(window(), String::formatted("Invalid IPv4 address for adapter {}", adapter_data.key));
|
||||
return;
|
||||
}
|
||||
if (!IPv4Address::from_string(adapter_data.value.default_gateway).has_value()) {
|
||||
GUI::MessageBox::show_error(window(), String::formatted("Invalid IPv4 gateway for adapter {}", adapter_data.key));
|
||||
return;
|
||||
}
|
||||
}
|
||||
config_file->write_entry(adapter_data.key, "IPv4Address", adapter_data.value.ip_address);
|
||||
config_file->write_entry(adapter_data.key, "IPv4Netmask", netmask);
|
||||
config_file->write_entry(adapter_data.key, "IPv4Gateway", adapter_data.value.default_gateway);
|
||||
}
|
||||
|
||||
GUI::Process::spawn_or_show_error(window(), "/bin/NetworkServer");
|
||||
|
||||
if (may_need_to_reboot)
|
||||
GUI::MessageBox::show(window(), "You may need to reboot for changes to take effect.", "Network Settings", GUI::MessageBox::Type::Warning);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Maciej <sppmacd@pm.me>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/SettingsWindow.h>
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
|
||||
namespace NetworkSettings {
|
||||
|
||||
class NetworkSettingsWidget : public GUI::SettingsWindow::Tab {
|
||||
C_OBJECT(NetworkSettingsWidget)
|
||||
|
||||
public:
|
||||
virtual void apply_settings() override;
|
||||
|
||||
private:
|
||||
NetworkSettingsWidget();
|
||||
|
||||
struct NetworkAdapterData {
|
||||
bool enabled = false;
|
||||
bool dhcp = false;
|
||||
String ip_address;
|
||||
int cidr = 0;
|
||||
String default_gateway;
|
||||
};
|
||||
|
||||
void on_switch_adapter(String const& adapter);
|
||||
void on_switch_enabled_or_dhcp();
|
||||
|
||||
HashMap<String, NetworkAdapterData> m_network_adapters;
|
||||
Vector<String> m_adapter_names;
|
||||
NetworkAdapterData* m_current_adapter_data = nullptr;
|
||||
|
||||
RefPtr<GUI::CheckBox> m_enabled_checkbox;
|
||||
RefPtr<GUI::CheckBox> m_dhcp_checkbox;
|
||||
RefPtr<GUI::ComboBox> m_adapters_combobox;
|
||||
RefPtr<GUI::TextBox> m_ip_address_textbox;
|
||||
RefPtr<GUI::SpinBox> m_cidr_spinbox;
|
||||
RefPtr<GUI::TextBox> m_default_gateway_textbox;
|
||||
};
|
||||
|
||||
}
|
46
Userland/Applications/NetworkSettings/main.cpp
Normal file
46
Userland/Applications/NetworkSettings/main.cpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Maciej <sppmacd@pm.me>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "NetworkSettingsWidget.h"
|
||||
#include <LibGUI/MessageBox.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibGUI/Application.h>
|
||||
#include <LibGUI/Icon.h>
|
||||
#include <LibGUI/SettingsWindow.h>
|
||||
#include <LibMain/Main.h>
|
||||
|
||||
ErrorOr<int> serenity_main(Main::Arguments args)
|
||||
{
|
||||
TRY(Core::System::pledge("stdio rpath wpath cpath recvfd sendfd unix proc exec"));
|
||||
|
||||
TRY(Core::System::unveil("/bin/NetworkServer", "x"));
|
||||
TRY(Core::System::unveil("/etc/Network.ini", "rwc"));
|
||||
TRY(Core::System::unveil("/proc/net/adapters", "r"));
|
||||
TRY(Core::System::unveil("/res", "r"));
|
||||
TRY(Core::System::unveil("/tmp/portal/clipboard", "rw"));
|
||||
TRY(Core::System::unveil("/tmp/portal/window", "rw"));
|
||||
TRY(Core::System::unveil(nullptr, nullptr));
|
||||
|
||||
auto app = TRY(GUI::Application::try_create(args));
|
||||
|
||||
if (getuid() != 0) {
|
||||
GUI::MessageBox::show_error(nullptr, "You need to be root to run Network Settings!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
TRY(Core::System::pledge("stdio rpath wpath cpath recvfd sendfd proc exec"));
|
||||
|
||||
auto app_icon = GUI::Icon::default_icon("network");
|
||||
auto window = TRY(GUI::SettingsWindow::create("Network Settings", GUI::SettingsWindow::ShowDefaultsButton::No));
|
||||
(void)TRY(window->add_tab<NetworkSettings::NetworkSettingsWidget>("Network", "network"));
|
||||
window->set_icon(app_icon.bitmap_for_size(16));
|
||||
|
||||
window->show();
|
||||
return app->exec();
|
||||
}
|
|
@ -87,8 +87,10 @@ ErrorOr<Icon> Icon::try_create_default_icon(StringView name)
|
|||
if (auto bitmap_or_error = Gfx::Bitmap::try_load_from_file(String::formatted("/res/icons/32x32/{}.png", name)); !bitmap_or_error.is_error())
|
||||
bitmap32 = bitmap_or_error.release_value();
|
||||
|
||||
if (!bitmap16 && !bitmap32)
|
||||
if (!bitmap16 && !bitmap32) {
|
||||
dbgln("Default icon not found: {}", name);
|
||||
return Error::from_string_literal("Default icon not found"sv);
|
||||
}
|
||||
|
||||
return Icon(move(bitmap16), move(bitmap32));
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue