Add Rust build code instead of Python, add dummy Renderer and "render" using it, add Tab new function, fix tls/tcp enum not working and bunch of other borrowing/owning issues in connection, and remove the thread for html cache parsing

This commit is contained in:
csd4ni3l
2026-02-22 17:14:41 +01:00
parent 0b2e9c12ad
commit 82ecd8cf8f
5 changed files with 142 additions and 88 deletions

3
.cargo/config.toml Normal file
View File

@@ -0,0 +1,3 @@
[target.'cfg(target_os = "linux")']
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"]

View File

@@ -11,79 +11,56 @@ jobs:
include: include:
- os: ubuntu-22.04 - os: ubuntu-22.04
platform: linux platform: linux
python-version: "3.11"
- os: windows-latest - os: windows-latest
platform: windows platform: windows
python-version: "3.11"
steps: steps:
- name: Check-out repository - name: Check-out repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Python - name: Cache
uses: actions/setup-python@v5 uses: actions/cache@v4
with: with:
python-version: "3.11" path: |
architecture: "x64" ~/.cargo/registry
cache: "pip" ~/.cargo/git
cache-dependency-path: | target
**/requirements*.txt key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install Dependencies - name: Install mold, clang, Wayland, ALSA and x11 headers and dependencies (Linux)
run: pip install -r requirements.txt
- name: Build Executable
uses: Nuitka/Nuitka-Action@main
with:
nuitka-version: main
script-name: run.py
nofollow-import-to: "*tk*,_codecs,encodings,multiprocessing,gi"
disable-plugins: tk-inter,dill-compat,eventlet,gevent,pyqt5,pyqt6,pyside2,pyside6,delvewheel,pywebview,matplotlib,spacy,enum-compat,pbr-compat,gevent,pmw-freezer,transformers,upx,kivy,options-nanny,multiprocessing,gi
include-data-dir: assets=assets
include-data-files: CREDITS=CREDITS
mode: onefile
output-file: csd4ni3lBrowser
- name: Locate and rename executable (Linux)
if: matrix.os == 'ubuntu-22.04' if: matrix.os == 'ubuntu-22.04'
run: | run: |
set -euo pipefail sudo apt update
echo "Searching for built Linux binary..." sudo apt install -y build-essential clang cmake pkg-config mold \
# List to help debugging when paths change libwayland-dev libxkbcommon-dev libegl1-mesa-dev \
ls -laR . | head -n 500 || true libwayland-egl-backend-dev \
BIN=$(find . -maxdepth 4 -type f -name 'csd4ni3lBrowser*' -perm -u+x | head -n1 || true) libx11-dev libxext-dev libxrandr-dev libxinerama-dev libxcursor-dev \
if [ -z "${BIN}" ]; then libxi-dev libxfixes-dev libxrender-dev \
echo "ERROR: No Linux binary found after build" libfreetype6-dev libfontconfig1-dev libgl1-mesa-dev \
exit 1 libasound2-dev libudev-dev
fi
echo "Found: ${BIN}"
mkdir -p build_output
cp "${BIN}" build_output/csd4ni3lBrowser.bin
chmod +x build_output/csd4ni3lBrowser.bin
echo "Executable ready: build_output/csd4ni3lBrowser.bin"
shell: bash shell: bash
- name: Locate and rename executable (Windows) - name: Build
run: cargo build --release --verbose
- name: Verify executable (Linux)
if: matrix.os == 'ubuntu-22.04'
run: test target/release/soundboard
shell: bash
- name: Verify executable (Windows)
if: matrix.os == 'windows-latest' if: matrix.os == 'windows-latest'
run: | run: Test-Path target\release\soundboard.exe
Write-Host "Searching for built Windows binary..."
Get-ChildItem -Recurse -File -Filter 'csd4ni3lBrowser*.exe' | Select-Object -First 1 | ForEach-Object {
Write-Host ("Found: " + $_.FullName)
New-Item -ItemType Directory -Force -Path build_output | Out-Null
Copy-Item $_.FullName "build_output\csd4ni3lBrowser.exe"
Write-Host "Executable ready: build_output\csd4ni3lBrowser.exe"
}
if (!(Test-Path build_output\csd4ni3lBrowser.exe)) {
Write-Error "ERROR: No Windows binary found after build"
exit 1
}
shell: pwsh shell: pwsh
- name: Upload build artifact - name: Upload build artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ${{ matrix.platform }} name: ${{ matrix.platform }}
path: build_output/csd4ni3lBrowser.* path: |
target/release/soundboard
target/release/soundboard.exe
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -114,5 +91,5 @@ jobs:
--notes "Automated build for $TAG" --notes "Automated build for $TAG"
fi fi
# Upload the executables directly (no zip files) # Upload the executables directly (no zip files)
gh release upload "$TAG" downloads/linux/csd4ni3lBrowser.bin --clobber gh release upload "$TAG" downloads/linux/soundboard --clobber
gh release upload "$TAG" downloads/windows/csd4ni3lBrowser.exe --clobber gh release upload "$TAG" downloads/windows/soundboard.exe --clobber

View File

@@ -1,6 +1,36 @@
use std::{collections::HashMap, net::TcpStream, io::{Read, Write}, fs, thread}; use std::{collections::HashMap, net::TcpStream, io::{Read, Write}, fs};
use native_tls::TlsConnector; use native_tls::{TlsConnector, TlsStream};
use crate::html_parser::{Node, Rule}; use crate::http_client::html_parser::{Node, Rule};
enum Connection {
Plain(TcpStream),
Tls(TlsStream<TcpStream>),
}
impl Read for Connection {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self {
Connection::Plain(s) => s.read(buf),
Connection::Tls(s) => s.read(buf),
}
}
}
impl Write for Connection {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
Connection::Plain(s) => s.write(buf),
Connection::Tls(s) => s.write(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match self {
Connection::Plain(s) => s.flush(),
Connection::Tls(s) => s.flush(),
}
}
}
pub fn resolve_url(scheme: &str, host: &str, port: u16, path: &str, mut url: &str) -> String { pub fn resolve_url(scheme: &str, host: &str, port: u16, path: &str, mut url: &str) -> String {
if url.contains("://") { if url.contains("://") {
@@ -29,7 +59,6 @@ pub fn resolve_url(scheme: &str, host: &str, port: u16, path: &str, mut url: &st
format!("{}://{}:{}{}", scheme, host, port, resolved_path) format!("{}://{}:{}{}", scheme, host, port, resolved_path)
} }
} }
pub struct HTTPClient { pub struct HTTPClient {
pub scheme: String, pub scheme: String,
pub host: String, pub host: String,
@@ -46,16 +75,16 @@ pub struct HTTPClient {
pub view_source: bool, pub view_source: bool,
pub redirect_count: u32, pub redirect_count: u32,
pub needs_render: bool, pub needs_render: bool,
pub tcp_stream: Option<Box<dyn Read + Write>> pub tcp_stream: Option<Connection>
} }
impl HTTPClient { impl HTTPClient {
pub fn new(scheme: String, host: String, path: String, port: u32) -> HTTPClient { pub fn new() -> HTTPClient {
HTTPClient { HTTPClient {
scheme, scheme: String::new(),
host, host: String::new(),
path, path: String::new(),
port, port: 0,
request_headers: HashMap::new(), request_headers: HashMap::new(),
response_explanation: None, response_explanation: None,
response_headers: HashMap::new(), response_headers: HashMap::new(),
@@ -76,9 +105,9 @@ impl HTTPClient {
} }
pub fn get_request(&mut self, url: &String, headers: HashMap<String, String>, css: bool) { pub fn get_request(&mut self, url: &String, headers: HashMap<String, String>, css: bool) {
let mut parsed_url = url; let mut parsed_url = url.clone();
if parsed_url.starts_with("view-source:") { if parsed_url.starts_with("view-source:") {
parsed_url = parsed_url.split_once("view-source:")[1]; parsed_url = parsed_url.split_once("view-source:").unwrap().1.to_string();
self.view_source = true; self.view_source = true;
} }
else { else {
@@ -98,7 +127,8 @@ impl HTTPClient {
} }
if self.host.contains(":") { if self.host.contains(":") {
let (host_str, port_str) = self.host.split_once(":").unwrap(); let temp_host = self.host.clone();
let (host_str, port_str) = temp_host.split_once(":").unwrap();
self.host = host_str.to_string(); self.host = host_str.to_string();
self.port = port_str.parse().unwrap(); self.port = port_str.parse().unwrap();
@@ -121,15 +151,12 @@ impl HTTPClient {
self.tcp_stream = None; self.tcp_stream = None;
if !self.request_headers.contains_key("Host") { if !self.request_headers.contains_key("Host") {
self.request_headers["Host"] = self.host; self.request_headers.insert("Host".to_string(), self.host.clone());
} }
let cache_filename = format!("{}_{}_{}_{}.html", self.scheme, self.host, self.port, self.path.replace("/", "_")); let cache_filename = format!("{}_{}_{}_{}.html", self.scheme, self.host, self.port, self.path.replace("/", "_"));
if std::fs::exists(format!("html_cache/{}", cache_filename)).unwrap() { if std::fs::exists(format!("html_cache/{}", cache_filename)).unwrap() {
std::thread(move || { self.parse();
self.parse()
});
return; return;
} }
@@ -137,10 +164,10 @@ impl HTTPClient {
if self.scheme == "https" { if self.scheme == "https" {
let connector = TlsConnector::new().unwrap(); let connector = TlsConnector::new().unwrap();
self.tcp_stream = Some(Box::new(connector.connect(self.host.as_str(), stream).unwrap())); self.tcp_stream = Some(Connection::Tls(connector.connect(self.host.as_str(), tcp).unwrap()));
} }
else { else {
self.tcp_stream = Some(Box::new(tcp)); self.tcp_stream = Some(Connection::Plain(tcp));
} }
} }
@@ -179,7 +206,7 @@ impl HTTPClient {
self.response_headers = headers; self.response_headers = headers;
} }
pub fn parse(self) { pub fn parse(&mut self) {
} }
} }

View File

@@ -0,0 +1,37 @@
use crate::http_client::connection::HTTPClient;
use bevy_egui::egui::Ui;
enum Widget {
}
struct DocumentLayout {
}
pub struct Renderer {
content: String,
request_scheme: String,
scroll_y: f64,
scroll_y_speed: f64,
smallest_y: f64,
document: Option<DocumentLayout>,
widgets: Vec<Widget>
}
impl Renderer {
pub fn new() -> Renderer {
Renderer {
content: String::new(),
request_scheme: String::new(),
scroll_y: 0.0,
scroll_y_speed: 50.0,
smallest_y: 0.0,
document: None,
widgets: Vec::new()
}
}
pub fn render(&mut self, http_client: &HTTPClient, ui: &mut Ui) {
}
}

View File

@@ -5,15 +5,27 @@ use bevy_egui::{EguiContextSettings, EguiContexts, EguiPrimaryContextPass, EguiS
pub mod constants; pub mod constants;
pub mod http_client; pub mod http_client;
use crate::constants::DEFAULT_HEADERS; use crate::constants::DEFAULT_HEADERS;
use crate::http_client::{connection::HTTPClient, *}; use crate::http_client::{connection::HTTPClient, renderer::Renderer};
struct Tab { struct Tab {
url: String, url: String,
title: String, title: String,
http_client: HTTPClient http_client: HTTPClient,
renderer: Renderer
} }
impl Tab { impl Tab {
fn new(url: &str) -> Tab {
let http_client = HTTPClient::new();
Tab {
url: url.to_string(),
title: url.to_string(),
http_client,
renderer: Renderer::new()
}
}
fn request(&mut self, url: String) { fn request(&mut self, url: String) {
self.url = url; self.url = url;
if self.url.starts_with("http://") || self.url.starts_with("https://") || self.url.starts_with("view-source:") { if self.url.starts_with("http://") || self.url.starts_with("https://") || self.url.starts_with("view-source:") {
@@ -40,11 +52,7 @@ struct AppState {
} }
fn main() { fn main() {
let new_tab = Tab { let new_tab = Tab::new("about:blank");
url: "about:blank".to_string(),
title: "about:blank".to_string(),
http_client: HTTPClient::new("about".to_string(), "blank".to_string(), "".to_string(), 0)
};
let mut tabs = Vec::new(); let mut tabs = Vec::new();
tabs.push(new_tab); tabs.push(new_tab);
@@ -122,11 +130,7 @@ fn draw(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Result {
} }
if ui.button("+" ).clicked() { if ui.button("+" ).clicked() {
let new_tab = Tab { let new_tab = Tab::new("about:blank");
url: "about:blank".to_string(),
title: "about:blank".to_string(),
http_client: HTTPClient::new("about".to_string(), "blank".to_string(), "".to_string(), 0)
};
app_state.tabs.push(new_tab); app_state.tabs.push(new_tab);
} }
@@ -137,6 +141,12 @@ fn draw(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Result {
ui.add_sized([available_width, available_height / 20.0], egui::TextEdit::singleline(&mut app_state.current_url)).request_focus(); ui.add_sized([available_width, available_height / 20.0], egui::TextEdit::singleline(&mut app_state.current_url)).request_focus();
}); });
egui::CentralPanel::default().show(ctx, |ui| {
let active_tab_index = app_state.active_tab.clone();
let tab = &mut app_state.tabs[active_tab_index];
tab.renderer.render(&tab.http_client, ui);
});
Ok(()) Ok(())
} }