Skip to content

Commit

Permalink
async actions
Browse files Browse the repository at this point in the history
  • Loading branch information
deanlee committed Jan 14, 2025
1 parent 2356a70 commit 3846500
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 75 deletions.
5 changes: 4 additions & 1 deletion system/ui/raylib/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ App::App(const char *title, int fps) {
// Load fonts
fonts_.reserve(FONT_FILE_PATHS.size());
for (int i = 0; i < FONT_FILE_PATHS.size(); ++i) {
fonts_.push_back(LoadFontEx(FONT_FILE_PATHS[i], 120, nullptr, 250));
fonts_.push_back(LoadFontEx(FONT_FILE_PATHS[i], 260, nullptr, 0));
// Improve font texture quality with mipmaps and bilinear filtering
GenTextureMipmaps(&fonts_[i].texture);
SetTextureFilter(fonts_[i].texture, TEXTURE_FILTER_BILINEAR);
}

pApp = this;
Expand Down
2 changes: 1 addition & 1 deletion system/ui/raylib/wifi_manager/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ int main() {
BeginDrawing();
ClearBackground(RAYLIB_BLACK);
GuiLabel({40, 20, 300, 40}, "Wi-Fi Manager");
wifi_manager.draw({40, 100, GetScreenWidth() - 80.0f, GetScreenHeight() - 180.0f});
wifi_manager.render({40, 100, GetScreenWidth() - 80.0f, GetScreenHeight() - 180.0f});
EndDrawing();
}
return 0;
Expand Down
27 changes: 19 additions & 8 deletions system/ui/raylib/wifi_manager/nmcli_backend.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace wifi {

std::vector<Network> scan_networks() {
std::vector<Network> networks;
std::string output = util::check_output("nmcli -t -f SSID,IN-USE,SIGNAL,SECURITY --colors no device wifi list");
std::string output = util::check_output("nmcli -t -c no -f SSID,IN-USE,SIGNAL,SECURITY device wifi list");

for (const auto& line : split(output, '\n')) {
auto fields = split(line, ':');
Expand All @@ -37,17 +37,28 @@ std::vector<Network> scan_networks() {
}
}

std::sort(networks.begin(), networks.end(), [](const Network& a, const Network& b) {
return std::tie(b.connected, b.strength, b.ssid) < std::tie(a.connected, a.strength, a.ssid);
});

std::sort(networks.begin(), networks.end());
return networks;
}

std::set<std::string> saved_networks() {
std::string cmd = "nmcli -t -f NAME,TYPE --colors no connection show | grep \":802-11-wireless\" | sed 's/^Auto //g' | cut -d':' -f1";
auto networks = split(util::check_output(cmd), '\n');
return std::set<std::string>(networks.begin(), networks.end());
std::set<std::string> result;
std::string cmd = R"(nmcli -t -c no -f NAME,TYPE connection show | grep 802-11-wireless)";
std::array<std::string, 2> prefixes{"openpilot connection ", "Auto "};
auto lines = split(util::check_output(cmd), '\n');
for (auto& line : lines) {
if (line.empty()) continue;

auto name = split(line, ':')[0];
for (const auto& prefix : prefixes) {
if (name.find(prefix) == 0) {
name.erase(0, prefix.length());
break;
}
}
result.insert(name);
}
return result;
}

bool connect(const std::string& ssid, const std::string& password) {
Expand Down
7 changes: 7 additions & 0 deletions system/ui/raylib/wifi_manager/nmcli_backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <set>
#include <string>
#include <tuple>
#include <vector>

enum class SecurityType {
Expand All @@ -17,9 +18,15 @@ struct Network {
SecurityType security_type;
};

inline bool operator<(const Network& lhs, const Network& rhs) {
return std::tie(rhs.connected, rhs.strength, rhs.ssid) < std::tie(lhs.connected, lhs.strength, lhs.ssid);
}

namespace wifi {

std::vector<Network> scan_networks();
std::set<std::string> saved_networks();
bool connect(const std::string& ssid, const std::string& password = "");
bool forget(const std::string& ssid);

} // namespace wifi
169 changes: 116 additions & 53 deletions system/ui/raylib/wifi_manager/wifi_manager.cc
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
#include "system/ui/raylib/wifi_manager/wifi_manager.h"

#include <algorithm>
#include <chrono>

#include "system/ui/raylib/util.h"
#define RAYGUI_IMPLEMENTATION
#define BLANK RAYLIB_BLANK
#define RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT 50
#define RAYGUI_MESSAGEBOX_BUTTON_HEIGHT 100
#define RAYGUI_MESSAGEBOX_BUTTON_PADDING 30
#define RAYGUI_TEXTINPUTBOX_BUTTON_PADDING 50
#define RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT 100
#define RAYGUI_TEXTINPUTBOX_HEIGHT 40

#include "third_party/raylib/include/raygui.h"

WifiManager::WifiManager() {
Expand All @@ -17,45 +25,48 @@ WifiManager::WifiManager() {
scanNetworksAsync();
}

void WifiManager::draw(const Rectangle& rect) {
void WifiManager::render(const Rectangle& rect) {
rescanIfNeeded();

std::unique_lock lock(mutex_);
if (wifi_networks_.empty()) {
GuiDrawText("Loading Wi-Fi networks...", rect, TEXT_ALIGN_CENTER, RAYLIB_WHITE);
return;
}

if (requires_password_) {
showPasswordDialog();
} else {
drawNetworkList(rect);
if (current_action_ == ActionState::Forget) {
if (!forgetNetwork()) return;
}
if (current_action_ == ActionState::Connect) {
if (!connectToNetwork()) return;
}

renderNetworkList(rect);
}

void WifiManager::rescanIfNeeded() {
double current_time = GetTime();
if (current_time - last_scan_time_ > 60.0) { // Rescan after 1 minute
if (current_action_ == ActionState::None && current_time - last_scan_time_ > 60.0) {
last_scan_time_ = current_time;
scanNetworksAsync();
}
}

void WifiManager::drawNetworkList(const Rectangle& rect) {
Rectangle content_rect = {rect.x, rect.y, rect.width - 20, wifi_networks_.size() * item_height_};
void WifiManager::renderNetworkList(const Rectangle& rect) {
if (available_networks_.empty()) {
GuiDrawText("Loading Wi-Fi networks...", rect, TEXT_ALIGN_CENTER, RAYLIB_WHITE);
return;
}

Rectangle content_rect = {rect.x, rect.y, rect.width - 20, available_networks_.size() * item_height_};
Rectangle scissor = {0};
GuiScrollPanel(rect, nullptr, content_rect, &scroll_offset_, &scissor);

const int padding = 20;
BeginScissorMode(scissor.x, scissor.y, scissor.width, scissor.height);

for (size_t i = 0; i < wifi_networks_.size(); ++i) {
const int padding = 20;
for (size_t i = 0; i < available_networks_.size(); ++i) {
float y = content_rect.y + i * item_height_ + scroll_offset_.y;
Rectangle item_rect = {content_rect.x + padding, y, content_rect.width - padding * 2, item_height_};

drawNetworkItem(item_rect, wifi_networks_[i]);
renderNetworkItem(item_rect, available_networks_[i]);

if (i != wifi_networks_.size() - 1) {
if (i != available_networks_.size() - 1) {
float line_y = item_rect.y + item_height_ - 1;
DrawLine(item_rect.x, line_y, item_rect.x + item_rect.width, line_y, RAYLIB_LIGHTGRAY);
}
Expand All @@ -64,70 +75,122 @@ void WifiManager::drawNetworkList(const Rectangle& rect) {
EndScissorMode();
}

void WifiManager::drawNetworkItem(const Rectangle& rect, const Network& network) {
void WifiManager::renderNetworkItem(const Rectangle& rect, const Network& network) {
const int btn_width = 200;
Rectangle label_rect{rect.x, rect.y, rect.width - btn_width * 2, item_height_};
GuiLabel(label_rect, network.ssid.c_str());

if (network.connected) {
GuiLabel({rect.x + rect.width - btn_width * 2 - 20, rect.y, btn_width, item_height_}, "Connected");
} else if (CheckCollisionPointRec(GetMousePosition(), label_rect) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
initiateConnection(network.ssid);
Rectangle state_rect = {rect.x + rect.width - btn_width * 2 - 30, rect.y, btn_width, item_height_};
if (network.connected && current_action_ != ActionState::Connecting) {
GuiLabel(state_rect, "Connected");
} else if (current_action_ == ActionState::Connecting && selected_network_->ssid == network.ssid) {
GuiLabel(state_rect, "CONNECTING...");
} else if (current_action_ == ActionState::None &&
CheckCollisionPointRec(GetMousePosition(), label_rect) &&
IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
initiateAction(network, ActionState::Connect);
}

if (saved_networks_.count(network.ssid)) {
if (GuiButton({rect.x + rect.width - btn_width, rect.y + (item_height_ - 80) / 2, btn_width, 80}, "Forget")) {
forgetNetwork(network.ssid);
if (GuiButton({rect.x + rect.width - btn_width, rect.y + (item_height_ - 80) / 2, btn_width, 80}, "Forget") &&
current_action_ == ActionState::None) {
initiateAction(network, ActionState::Forget);
}
}
}

void WifiManager::initiateConnection(const std::string& ssid) {
if (saved_networks_.count(ssid)) {
wifi::connect(ssid); // Directly connect to saved network
return;
void WifiManager::initiateAction(const Network& network, ActionState action) {
current_action_ = action;
selected_network_ = network;
}

bool WifiManager::forgetNetwork() {
int result = GuiMessageBox(
{GetScreenWidth() / 2.0f - 512, GetScreenHeight() / 2.0f - 384, 1024, 768},
("Forget " + selected_network_->ssid + "?").c_str(), "Are you sure you want to forget this network?", "Yes;No");

if (result < 0) return false;

selected_network_.reset();
if (result != 1) {
current_action_ = ActionState::None;
return true;
}

connecting_ssid_ = ssid;
memset(password_input_, 0, sizeof(password_input_));
requires_password_ = true;
}
current_action_ = ActionState::Forgetting;
saved_networks_.erase(selected_network_->ssid);
for (auto& n : available_networks_) {
if (n.ssid == selected_network_->ssid) {
n.connected = false;
break;
}
}

void WifiManager::forgetNetwork(const std::string& ssid) {
wifi::forget(ssid);
saved_networks_.erase(ssid);
scanNetworksAsync();
async_forget_task_ = std::async(std::launch::async, [this, ssid = selected_network_->ssid]() {
wifi::forget(ssid);
scanNetworksAsync();
current_action_ = ActionState::None;
});
return true;
}

void WifiManager::showPasswordDialog() {
// TODO: Implement a keyboard input dialog
int result = GuiTextInputBox(
{GetScreenWidth() / 2.0f - 120, GetScreenHeight() / 2.0f - 60, 240, 140},
("Connect to " + connecting_ssid_).c_str(), "Password:", "Ok;Cancel", password_input_, 128, NULL);
if (result < 0) return;

if (result == 1) {
if (wifi::connect(connecting_ssid_, password_input_)) {
saved_networks_.insert(connecting_ssid_);
bool WifiManager::connectToNetwork() {
std::string password;
if (!saved_networks_.count(selected_network_->ssid) && selected_network_->security_type != SecurityType::OPEN) {
// TODO: Implement a software keyboard input dialog
int result = GuiTextInputBox(
{GetScreenWidth() / 2.0f - 512, GetScreenHeight() / 2.0f - 200, 1024, 400},
("Connect to " + selected_network_->ssid).c_str(), "Password:", "Ok;Cancel", password_input_buffer_, 128, NULL);
if (result < 0) return false;

password = password_input_buffer_;
memset(password_input_buffer_, 0, sizeof(password_input_buffer_));
if (result != 1) {
selected_network_.reset();
current_action_ = ActionState::None;
return true;
}
}

connecting_ssid_.clear();
requires_password_ = false;
connectToNetworkAsync(selected_network_->ssid, password);
current_action_ = ActionState::Connecting;
return true;
}

void WifiManager::scanNetworksAsync() {
// Check if the previous scan is still running
if (async_task_.valid() && async_task_.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
if (async_scan_task_.valid() && async_scan_task_.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
return;
}

async_task_ = std::async(std::launch::async, [this]() {
auto networks = wifi::scan_networks();
async_scan_task_ = std::async(std::launch::async, [this]() {
auto scanned_networks = wifi::scan_networks();
auto known_networks = wifi::saved_networks();

std::unique_lock lock(mutex_);
wifi_networks_ = std::move(networks);
available_networks_ = std::move(scanned_networks);
saved_networks_ = std::move(known_networks);
});
}

void WifiManager::connectToNetworkAsync(const std::string& ssid, const std::string& password) {
if (async_connection_task_.valid() && async_connection_task_.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
return;
}

async_connection_task_ = std::async(std::launch::async, [this, ssid, password]() {
if (wifi::connect(ssid, password)) {
std::unique_lock lock(mutex_);
saved_networks_.insert(ssid);
selected_network_.reset();
current_action_ = ActionState::None;

for (auto& network : available_networks_) {
network.connected = (network.ssid == ssid);
}
std::sort(available_networks_.begin(), available_networks_.end());
} else {
selected_network_->security_type = SecurityType::WPA;
current_action_ = ActionState::Connect;
}
});
}
37 changes: 25 additions & 12 deletions system/ui/raylib/wifi_manager/wifi_manager.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#pragma once

#include <atomic>
#include <future>
#include <mutex>
#include <optional>
#include <thread>

#include "system/ui/raylib/raylib.h"
Expand All @@ -10,26 +12,37 @@
class WifiManager {
public:
WifiManager();
void draw(const Rectangle &rect);
void render(const Rectangle &rect);

protected:
void showPasswordDialog();
enum class ActionState {
None,
Connect,
Connecting,
Forget,
Forgetting
};

bool connectToNetwork();
void scanNetworksAsync();
void rescanIfNeeded();
void drawNetworkList(const Rectangle &rect);
void drawNetworkItem(const Rectangle& rect, const Network &network);
void initiateConnection(const std::string &ssid);
void forgetNetwork(const std::string& ssid);
bool forgetNetwork();
void renderNetworkList(const Rectangle &rect);
void renderNetworkItem(const Rectangle& rect, const Network &network);
void connectToNetworkAsync(const std::string &ssid, const std::string &password = "");
void initiateAction(const Network& network, ActionState action);

std::mutex mutex_;
std::future<void> async_task_;
std::vector<Network> wifi_networks_;
std::atomic<ActionState> current_action_ = ActionState::None;
std::future<void> async_scan_task_;
std::future<void> async_connection_task_;
std::future<void> async_forget_task_;
std::vector<Network> available_networks_;
std::set<std::string> saved_networks_;
double last_scan_time_ = 0;
std::optional<Network> selected_network_;

Vector2 scroll_offset_ = {0, 0};
std::string connecting_ssid_;
const float item_height_ = 160;
bool requires_password_ = false;
char password_input_[128] = {};
char password_input_buffer_[128] = {};
double last_scan_time_ = 0;
};

0 comments on commit 3846500

Please sign in to comment.