diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 2dbb60e..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(mkdir:*)", - "Bash(ninja:*)", - "Bash(git add:*)" - ] - } -} diff --git a/.gitignore b/.gitignore index 51d5ddb..19e58af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # Build directories +.cache/ build/ builddir/ @@ -21,7 +22,6 @@ compile_commands.json *.bak .vscode/ .idea/ - # System files .DS_Store Thumbs.db @@ -30,18 +30,6 @@ Thumbs.db config.h *.edj -# Autotools (if used) -.deps/ -.libs/ -Makefile -Makefile.in -*.log -*.trs -autom4te.cache/ -config.status -configure -aclocal.m4 - # Core dumps core core.* diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 5c75391..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,333 +0,0 @@ -PRD — Enlightenment Wi-Fi Module (iwd Backend) -1. Overview -1.1 Purpose - -Create an Enlightenment module that manages Wi-Fi connections using iwd (Intel Wireless Daemon) as the backend, providing functionality similar to econnman, but without ConnMan. - -The module should: - - Integrate cleanly with Enlightenment (E17+) - - Use iwd’s D-Bus API directly - - Provide a simple, fast, and reliable Wi-Fi UI - - Follow Enlightenment UX conventions (gadget + popup) - -1.2 Motivation - - ConnMan is increasingly deprecated or undesired on many systems - - iwd is lightweight, fast, and widely adopted (Arch, Fedora, Debian) - - Enlightenment currently lacks a first-class iwd-based Wi-Fi module - - Users want a native, non-NM, non-ConnMan Wi-Fi solution - -2. Goals & Non-Goals -2.1 Goals - - Feature parity with basic econnman Wi-Fi features - - Zero dependency on NetworkManager or ConnMan - - D-Bus only (no shelling out to iwctl) - - Minimal background CPU/memory usage - - Robust behavior across suspend/resume and network changes - -2.2 Non-Goals - - Ethernet management - - VPN management - - Cellular (WWAN) support - - Advanced enterprise Wi-Fi UI (EAP tuning beyond basics) - -3. Target Users - - Enlightenment desktop users - - Minimalist / embedded systems using iwd - - Power users avoiding NetworkManager - - Distributions shipping iwd by default - -4. User Experience -4.1 Gadget (Shelf Icon) - - Status icon: - - Disconnected - - Connecting - - Connected (signal strength tiers) - - Error - - Tooltip: - - Current SSID - - Signal strength - - Security type - -4.2 Popup UI - -Triggered by clicking the gadget. -Sections: - - Current Connection - - SSID - - Signal strength - - IP (optional) - - Disconnect button - - Available Networks - - Sorted by: - - Known networks - - Signal strength - - Icons for: - - Open - - WPA2/WPA3 - - Connect button per network - - Actions - - Rescan - - Enable / Disable Wi-Fi - -4.3 Authentication Flow - - On selecting a secured network: - - Prompt for passphrase - - Optional “remember network” - - Errors clearly reported (wrong password, auth failed, etc.) - -5. Functional Requirements -5.1 Wi-Fi Control - - Enable / disable Wi-Fi (via iwd Powered) - - Trigger scan - - List available networks - - Connect to a network - - Disconnect from current network - -5.2 Network State Monitoring - - React to: - - Connection changes - - Signal strength changes - - Device availability - - iwd daemon restart - -5.3 Known Networks - - List known (previously connected) networks - - Auto-connect indication - - Forget network - -5.4 Error Handling - - iwd not running - - No wireless device - - Permission denied (polkit) - - Authentication failure - -6. Technical Requirements -6.1 Backend - - iwd via D-Bus - - Service: net.connman.iwd - - No external command execution - -6.2 D-Bus Interfaces Used (Non-Exhaustive) - - net.connman.iwd.Adapter - - net.connman.iwd.Device - - net.connman.iwd.Network - - net.connman.iwd.Station - - net.connman.iwd.KnownNetwork - -6.3 Permissions - - Requires polkit rules for: - - Scanning - - Connecting - - Forgetting networks - - Module must gracefully degrade without permissions - -7. Architecture -7.1 Module Structure - -e_iwd/ -├── e_mod_main.c -├── e_mod_config.c -├── e_mod_gadget.c -├── e_mod_popup.c -├── iwd/ -│ ├── iwd_manager.c -│ ├── iwd_device.c -│ ├── iwd_network.c -│ └── iwd_dbus.c -└── ui/ - ├── wifi_list.c - ├── wifi_auth.c - └── wifi_status.c - -7.2 Data Flow - -iwd (D-Bus) - ↓ -iwd_dbus.c - ↓ -iwd_manager / device / network - ↓ -UI layer (EFL widgets) - ↓ -Gadget / Popup - -7.3 Threading Model - - Single main loop - - Async D-Bus calls via EFL - - No blocking calls on UI thread - -8. State Model -8.1 Connection States - - OFF - - IDLE - - SCANNING - - CONNECTING - - CONNECTED - - ERROR - -8.2 Transitions - - Triggered by: - - User actions - - iwd signals - - System suspend/resume - -9. Configuration -9.1 Module Settings - - Auto-connect enabled / disabled - - Show hidden networks - - Signal strength refresh interval - - Preferred adapter (if multiple) - -Stored using Enlightenment module config system. -10. Performance & Reliability -10.1 Performance - - Startup time < 100 ms - - No periodic polling; signal-driven updates - - Minimal memory footprint (< 5 MB) - -10.2 Reliability - - Handle iwd restart gracefully - - Auto-rebind D-Bus objects - - Avoid crashes on device hot-plug - -11. Security Considerations - - Never log passphrases - - Passphrases only sent over D-Bus to iwd - - Respect system polkit policies - - No plaintext storage in module config - - -13. Success Metrics - - Successful connect/disconnect in ≥ 99% cases - - No UI freezes during scan/connect - - Parity with econnman Wi-Fi UX - - Adoption by at least one major distro Enlightenment spin - -14. Future Extensions (Out of Scope) - - Ethernet support - - VPN integration - - QR-based Wi-Fi sharing - - Per-network advanced EAP UI - -15. Open Questions / Risks - - Polkit UX integration (password prompts) - - Multiple adapter handling UX - - iwd API changes across versions diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 4907715..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,442 +0,0 @@ -# Contributing to eiwd - -Thank you for your interest in contributing to eiwd! This document provides guidelines and instructions for contributing. - -## Code of Conduct - -Be respectful, constructive, and professional in all interactions. We value: -- Clear communication -- Constructive criticism -- Collaborative problem-solving -- Quality over quantity - -## Ways to Contribute - -### Report Bugs - -Before submitting a bug report: -1. Check existing issues to avoid duplicates -2. Verify you're using the latest version -3. Test with a clean configuration - -Include in your report: -- **System Information**: - - Distribution and version - - Enlightenment version: `enlightenment -version` - - EFL version: `pkg-config --modversion elementary` - - iwd version: `iwd --version` - - Kernel version: `uname -r` - - Wireless chipset: `lspci | grep -i network` - -- **Steps to Reproduce**: Detailed, numbered steps -- **Expected Behavior**: What should happen -- **Actual Behavior**: What actually happens -- **Logs**: Relevant excerpts from: - - `~/.cache/enlightenment/enlightenment.log` - - `sudo journalctl -u iwd --since "30 minutes ago"` - -- **Screenshots**: If UI-related - -### Suggest Features - -Feature requests should include: -- Clear use case and motivation -- Expected behavior and UI mockups (if applicable) -- Potential implementation approach -- Why it benefits eiwd users - -Note: Features must align with the core goal of lightweight, fast Wi-Fi management via iwd. - -### Improve Documentation - -Documentation contributions are highly valued: -- Fix typos or unclear sections -- Add missing information -- Improve examples -- Translate to other languages - -Documentation files: -- `README.md` - Overview and quick start -- `INSTALL.md` - Detailed installation and troubleshooting -- `TESTING.md` - Testing procedures -- Code comments - Explain complex logic - -### Submit Code Changes - -Follow the development workflow below. - -## Development Workflow - -### 1. Set Up Development Environment - -```bash -# Install dependencies (Arch Linux example) -sudo pacman -S base-devel meson ninja enlightenment efl iwd git - -# Clone repository -git clone eiwd -cd eiwd - -# Create development branch -git checkout -b feature/your-feature-name -``` - -### 2. Make Changes - -Follow the coding standards below. Key principles: -- Keep changes focused and atomic -- Test thoroughly before committing -- Write clear commit messages -- Update documentation as needed - -### 3. Build and Test - -```bash -# Clean build -rm -rf build -meson setup build -ninja -C build - -# Run manual tests (see TESTING.md) -# At minimum: -# - Module loads without errors -# - Can scan and connect to networks -# - No crashes or memory leaks - -# Check for warnings -ninja -C build 2>&1 | grep -i warning -``` - -### 4. Commit Changes - -```bash -# Stage changes -git add src/your-changed-file.c - -# Commit with descriptive message -git commit -m "Add feature: brief description - -Detailed explanation of what changed and why. -Mention any related issues (#123). - -Tested on: [your system]" -``` - -### 5. Submit Pull Request - -```bash -# Push to your fork -git push origin feature/your-feature-name -``` - -Then create a pull request via GitHub/GitLab with: -- Clear title summarizing the change -- Description explaining motivation and implementation -- Reference to related issues -- Test results and system information - -## Coding Standards - -### C Code Style - -Follow Enlightenment/EFL conventions: - -```c -/* Function naming: module_subsystem_action */ -void iwd_network_connect(IWD_Network *net); - -/* Struct naming: Module prefix + descriptive name */ -typedef struct _IWD_Network -{ - const char *path; - const char *name; - Eina_Bool known; -} IWD_Network; - -/* Indentation: 3 spaces (no tabs) */ -void -function_name(int param) -{ - if (condition) - { - do_something(); - } -} - -/* Braces: Always use braces, even for single-line blocks */ -if (test) -{ - single_statement(); -} - -/* Line length: Aim for < 80 characters, max 100 */ - -/* Comments: Clear and concise */ -/* Check if device is powered on */ -if (dev->powered) -{ - /* Device is active, proceed with scan */ - iwd_device_scan(dev); -} -``` - -### File Organization - -``` -src/ -├── e_mod_*.c # Enlightenment module interface -├── iwd/ # iwd D-Bus backend -│ └── iwd_*.c # Backend implementation -└── ui/ # UI dialogs - └── wifi_*.c # Dialog implementations -``` - -Each file should: -- Include copyright/license header -- Include necessary headers (avoid unnecessary includes) -- Declare static functions before use (or use forward declarations) -- Group related functions together -- Use clear section comments - -### Header Files - -```c -#ifndef E_IWD_NETWORK_H -#define E_IWD_NETWORK_H - -#include -#include - -/* Public structures */ -typedef struct _IWD_Network IWD_Network; - -/* Public functions */ -IWD_Network *iwd_network_new(const char *path); -void iwd_network_free(IWD_Network *net); -void iwd_network_connect(IWD_Network *net); - -#endif -``` - -### Naming Conventions - -- **Functions**: `module_subsystem_action()` - e.g., `iwd_device_scan()` -- **Structures**: `Module_Descriptive_Name` - e.g., `IWD_Network` -- **Variables**: `descriptive_name` (lowercase, underscores) -- **Constants**: `MODULE_CONSTANT_NAME` - e.g., `IWD_SERVICE` -- **Static functions**: `_local_function_name()` - prefix with underscore - -### Memory Management - -```c -/* Use EFL macros for allocation */ -thing = E_NEW(Thing, 1); -things = E_NEW(Thing, count); -E_FREE(thing); - -/* Use eina_stringshare for strings */ -const char *str = eina_stringshare_add("text"); -eina_stringshare_del(str); - -/* For replaceable strings */ -eina_stringshare_replace(&existing, "new value"); - -/* Always check allocations */ -obj = E_NEW(Object, 1); -if (!obj) -{ - ERR("Failed to allocate Object"); - return NULL; -} -``` - -### Error Handling - -```c -/* Use logging macros */ -DBG("Debug info: %s", info); -INF("Informational message"); -WRN("Warning: potential issue"); -ERR("Error occurred: %s", error); - -/* Check return values */ -if (!iwd_dbus_init()) -{ - ERR("Failed to initialize D-Bus"); - return EINA_FALSE; -} - -/* Validate parameters */ -void iwd_device_scan(IWD_Device *dev) -{ - if (!dev || !dev->station_proxy) - { - ERR("Invalid device for scan"); - return; - } - /* ... */ -} -``` - -### D-Bus Operations - -```c -/* Always use async calls */ -eldbus_proxy_call(proxy, "MethodName", - callback_function, - callback_data, - -1, /* Timeout (-1 = default) */ - ""); /* Signature */ - -/* Handle errors in callbacks */ -static void -_callback(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending) -{ - const char *err_name, *err_msg; - - if (eldbus_message_error_get(msg, &err_name, &err_msg)) - { - ERR("D-Bus error: %s: %s", err_name, err_msg); - return; - } - /* Process response */ -} -``` - -### Security Considerations - -**CRITICAL**: Never log sensitive data - -```c -/* WRONG - Don't do this */ -DBG("Connecting with password: %s", password); - -/* CORRECT - Log actions without sensitive data */ -DBG("Connecting to network: %s", ssid); - -/* Clear sensitive data after use */ -if (passphrase) -{ - memset(passphrase, 0, strlen(passphrase)); - free(passphrase); -} -``` - -## Testing Requirements - -All code changes must: - -1. **Compile without warnings**: - ```bash - ninja -C build 2>&1 | grep warning - # Should return empty - ``` - -2. **Pass manual tests** (see TESTING.md): - - Module loads successfully - - Core functionality works (scan, connect, disconnect) - - No crashes during basic operations - -3. **No memory leaks** (for significant changes): - ```bash - valgrind --leak-check=full enlightenment_start - # Perform operations, check for leaks - ``` - -4. **Update documentation** if: - - Adding new features - - Changing behavior - - Modifying configuration - - Adding dependencies - -## Pull Request Checklist - -Before submitting: - -- [ ] Code follows style guidelines -- [ ] Compiles without warnings -- [ ] Tested on at least one distribution -- [ ] Documentation updated if needed -- [ ] Commit messages are clear and descriptive -- [ ] No debugging code left in (printfs, commented blocks) -- [ ] No unnecessary whitespace changes -- [ ] Sensitive data not logged - -## Review Process - -1. **Submission**: Create pull request with description -2. **Automated Checks**: CI runs (if configured) -3. **Code Review**: Maintainers review code -4. **Feedback**: Requested changes or approval -5. **Revision**: Address feedback and update PR -6. **Merge**: Approved changes merged to main - -Expect: -- Initial response within 7 days -- Constructive feedback -- Potential requests for changes -- Testing on maintainers' systems - -## Commit Message Guidelines - -Format: -``` -Component: Short summary (50 chars or less) - -Detailed explanation of changes (wrap at 72 chars). -Explain WHAT changed and WHY, not just HOW. - -If this fixes an issue: -Fixes #123 - -If this is related to an issue: -Related to #456 - -Tested on: Arch Linux, E 0.27.1, iwd 2.14 -``` - -Examples: -``` -iwd_network: Fix crash when connecting to hidden networks - -The connect_hidden function didn't validate the device pointer, -causing a segfault when called before device initialization. - -Added null check and error logging. - -Fixes #42 -Tested on: Gentoo, E 0.27.0 -``` - -## Feature Development Guidelines - -When adding features: - -1. **Discuss first**: Open an issue to discuss the feature -2. **Keep it focused**: One feature per PR -3. **Follow architecture**: Maintain separation between D-Bus layer, module interface, and UI -4. **Match existing patterns**: Study similar existing code -5. **Think about edge cases**: Handle errors, missing devices, permission issues -6. **Consider performance**: Avoid blocking operations, minimize polling -7. **Document**: Add comments, update user documentation - -## Getting Help - -- **Questions**: Open a GitHub discussion or issue -- **Stuck**: Ask in the issue/PR, provide context -- **Enlightenment API**: See [E API docs](https://docs.enlightenment.org/) -- **EFL API**: See [EFL API reference](https://docs.enlightenment.org/api/efl/start) -- **iwd D-Bus**: See [iwd documentation](https://iwd.wiki.kernel.org/) - -## License - -By contributing, you agree that your contributions will be licensed under the same license as the project. - -## Recognition - -Contributors are recognized in: -- Git commit history -- `AUTHORS` file (if created) -- Release notes for significant contributions - -Thank you for contributing to eiwd! diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100644 index 7dd209d..0000000 --- a/INSTALL.md +++ /dev/null @@ -1,490 +0,0 @@ -# Installation Guide - eiwd - -Detailed installation instructions for the eiwd Enlightenment Wi-Fi module. - -## Table of Contents - -1. [System Requirements](#system-requirements) -2. [Building from Source](#building-from-source) -3. [Installation](#installation) -4. [Configuration](#configuration) -5. [Troubleshooting](#troubleshooting) -6. [Uninstallation](#uninstallation) - -## System Requirements - -### Supported Distributions - -eiwd has been tested on: -- Arch Linux -- Gentoo Linux -- Debian/Ubuntu (with manual EFL installation) -- Fedora - -### Minimum Versions - -| Component | Minimum Version | Recommended | -|-----------|----------------|-------------| -| Enlightenment | 0.25.x | 0.27.x | -| EFL | 1.26.x | 1.28.x | -| iwd | 1.0 | Latest stable | -| Linux kernel | 4.14+ | 5.4+ | -| D-Bus | 1.10+ | 1.14+ | - -### Wireless Hardware - -Any wireless adapter supported by the Linux kernel and iwd: -- Intel Wi-Fi (best support) -- Atheros (ath9k, ath10k) -- Realtek (rtl8xxx series) -- Broadcom (with appropriate drivers) - -Check compatibility: `iwd --version` and `iwctl device list` - -## Building from Source - -### 1. Install Build Dependencies - -#### Arch Linux -```bash -sudo pacman -S base-devel meson ninja enlightenment efl iwd -``` - -#### Gentoo -```bash -sudo emerge --ask dev-util/meson dev-util/ninja enlightenment efl net-wireless/iwd -``` - -#### Debian/Ubuntu -```bash -sudo apt install build-essential meson ninja-build \ - libefl-all-dev enlightenment-dev iwd -``` - -#### Fedora -```bash -sudo dnf install @development-tools meson ninja-build \ - efl-devel enlightenment-devel iwd -``` - -### 2. Get Source Code - -```bash -# Clone repository -git clone eiwd -cd eiwd - -# Or extract tarball -tar xzf eiwd-0.1.0.tar.gz -cd eiwd-0.1.0 -``` - -### 3. Configure Build - -```bash -# Default configuration -meson setup build - -# Custom options -meson setup build \ - --prefix=/usr \ - --libdir=lib64 \ - -Dnls=true -``` - -Available options: -- `--prefix=PATH`: Installation prefix (default: `/usr/local`) -- `--libdir=NAME`: Library directory name (auto-detected) -- `-Dnls=BOOL`: Enable/disable translations (default: `true`) - -### 4. Compile - -```bash -ninja -C build -``` - -Expected output: -``` -[14/14] Linking target src/module.so -``` - -Verify compilation: -```bash -ls -lh build/src/module.so build/data/e-module-iwd.edj -``` - -Should show: -- `module.so`: ~230-240 KB -- `e-module-iwd.edj`: ~10-12 KB - -## Installation - -### System-Wide Installation - -```bash -# Install module -sudo ninja -C build install - -# On some systems, update library cache -sudo ldconfig -``` - -### Installation Paths - -Default paths (with `--prefix=/usr`): -``` -/usr/lib64/enlightenment/modules/iwd/ -├── linux-x86_64-0.27/ -│ ├── module.so -│ └── e-module-iwd.edj -└── module.desktop -``` - -The architecture suffix (`linux-x86_64-0.27`) matches your Enlightenment version. - -### Verify Installation - -```bash -# Check files -ls -R /usr/lib64/enlightenment/modules/iwd/ - -# Check module metadata -cat /usr/lib64/enlightenment/modules/iwd/module.desktop -``` - -## Configuration - -### 1. Enable the Module - -#### Via GUI: -1. Open Enlightenment Settings (Settings → Modules) -2. Find "IWD" or "IWD Wi-Fi" in the list -3. Select it and click "Load" -4. The module should show "Running" status - -#### Via Command Line: -```bash -# Enable module -enlightenment_remote -module-load iwd - -# Verify -enlightenment_remote -module-list | grep iwd -``` - -### 2. Add Gadget to Shelf - -1. Right-click on your shelf → Shelf → Contents -2. Find "IWD Wi-Fi" in the gadget list -3. Click to add it to the shelf -4. Position it as desired - -Alternatively: -1. Right-click shelf → Add → Gadget → IWD Wi-Fi - -### 3. Configure iwd Service - -```bash -# Enable iwd service -sudo systemctl enable iwd.service - -# Start iwd -sudo systemctl start iwd.service - -# Check status -systemctl status iwd.service -``` - -Expected output should show "active (running)". - -### 4. Disable Conflicting Services - -iwd conflicts with wpa_supplicant and NetworkManager's Wi-Fi management: - -```bash -# Stop and disable wpa_supplicant -sudo systemctl stop wpa_supplicant.service -sudo systemctl disable wpa_supplicant.service - -# If using NetworkManager, configure it to ignore Wi-Fi -sudo mkdir -p /etc/NetworkManager/conf.d/ -cat << EOF | sudo tee /etc/NetworkManager/conf.d/wifi-backend.conf -[device] -wifi.backend=iwd -EOF - -sudo systemctl restart NetworkManager -``` - -Or disable NetworkManager entirely: -```bash -sudo systemctl stop NetworkManager -sudo systemctl disable NetworkManager -``` - -### 5. Configure Permissions - -#### Polkit Rules - -Create `/etc/polkit-1/rules.d/50-iwd.rules`: - -```javascript -/* Allow users in 'wheel' group to manage Wi-Fi */ -polkit.addRule(function(action, subject) { - if (action.id.indexOf("net.connman.iwd.") == 0) { - if (subject.isInGroup("wheel")) { - return polkit.Result.YES; - } - } -}); -``` - -Adjust group as needed: -- Arch/Gentoo: `wheel` -- Debian/Ubuntu: `sudo` or `netdev` -- Fedora: `wheel` - -Reload polkit: -```bash -sudo systemctl restart polkit -``` - -#### Alternative: D-Bus Policy - -Create `/etc/dbus-1/system.d/iwd-custom.conf`: - -```xml - - - - - - - -``` - -Reload D-Bus: -```bash -sudo systemctl reload dbus -``` - -### 6. Module Configuration - -Right-click the gadget → Configure, or: -Settings → Modules → IWD → Configure - -Options: -- **Auto-connect to known networks**: Enabled by default -- **Show hidden networks**: Show "Hidden..." button -- **Signal refresh interval**: Update frequency (default: 5s) -- **Preferred adapter**: For systems with multiple wireless cards - -Configuration is saved automatically to: -`~/.config/enlightenment/module.iwd.cfg` - -## Troubleshooting - -### Module Won't Load - -**Symptom**: Module appears in list but won't load, or crashes on load. - -**Solutions**: -```bash -# Check Enlightenment logs -tail -f ~/.cache/enlightenment/enlightenment.log - -# Verify module dependencies -ldd /usr/lib64/enlightenment/modules/iwd/linux-x86_64-0.27/module.so - -# Ensure iwd is running -systemctl status iwd - -# Check D-Bus connection -dbus-send --system --print-reply \ - --dest=net.connman.iwd \ - / org.freedesktop.DBus.Introspectable.Introspect -``` - -### No Wireless Devices Shown - -**Symptom**: Gadget shows "No device" or red error state. - -**Solutions**: -```bash -# Check if iwd sees the device -iwctl device list - -# Verify wireless is powered on -rfkill list -# If blocked: -rfkill unblock wifi - -# Check kernel driver -lspci -k | grep -A 3 Network - -# Ensure device is not managed by other services -nmcli device status # Should show "unmanaged" -``` - -### Permission Denied Errors - -**Symptom**: Cannot scan or connect, error messages mention "NotAuthorized". - -**Solutions**: -```bash -# Check polkit rules -ls -la /etc/polkit-1/rules.d/50-iwd.rules - -# Verify group membership -groups $USER - -# Test D-Bus permissions manually -gdbus call --system \ - --dest net.connman.iwd \ - --object-path /net/connman/iwd \ - --method org.freedesktop.DBus.Introspectable.Introspect - -# Check audit logs (if available) -sudo journalctl -u polkit --since "1 hour ago" -``` - -### Connection Failures - -**Symptom**: Can scan but cannot connect to networks. - -**Solutions**: -```bash -# Enable iwd debug logging -sudo systemctl edit iwd.service -# Add: -# [Service] -# ExecStart= -# ExecStart=/usr/lib/iwd/iwd --debug - -sudo systemctl daemon-reload -sudo systemctl restart iwd - -# Check logs -sudo journalctl -u iwd -f - -# Test connection manually -iwctl station wlan0 connect "Your SSID" - -# Verify known networks -ls /var/lib/iwd/ -``` - -### Theme Not Loading - -**Symptom**: Gadget appears as colored rectangles instead of proper icons. - -**Solutions**: -```bash -# Verify theme file exists -ls -la /usr/lib64/enlightenment/modules/iwd/*/e-module-iwd.edj - -# Check file permissions -chmod 644 /usr/lib64/enlightenment/modules/iwd/*/e-module-iwd.edj - -# Test theme manually -edje_player /usr/lib64/enlightenment/modules/iwd/*/e-module-iwd.edj - -# Reinstall -sudo ninja -C build install -``` - -### Gadget Not Updating - -**Symptom**: Connection state doesn't change or networks don't appear. - -**Solutions**: -```bash -# Check D-Bus signals are being received -dbus-monitor --system "interface='net.connman.iwd.Device'" - -# Verify signal refresh interval (should be 1-60 seconds) -# Increase in module config if too low - -# Restart module -enlightenment_remote -module-unload iwd -enlightenment_remote -module-load iwd -``` - -### iwd Daemon Crashes - -**Symptom**: Gadget shows red error state intermittently. - -**Solutions**: -```bash -# Check iwd logs -sudo journalctl -u iwd --since "30 minutes ago" - -# Update iwd -sudo pacman -Syu iwd # Arch -sudo emerge --update iwd # Gentoo - -# Report bug with: -# - iwd version: iwd --version -# - Kernel version: uname -r -# - Wireless chip: lspci | grep Network -``` - -### Common Error Messages - -| Error Message | Cause | Solution | -|---------------|-------|----------| -| "Wi-Fi daemon (iwd) has stopped" | iwd service not running | `sudo systemctl start iwd` | -| "Permission Denied" dialog | Polkit rules not configured | Set up polkit (see Configuration) | -| "Failed to connect: operation failed" | Wrong password | Re-enter passphrase | -| "No device" in gadget | No wireless adapter detected | Check `rfkill`, drivers | - -## Uninstallation - -### Remove Module - -```bash -# From build directory -sudo ninja -C build uninstall -``` - -### Manual Removal - -```bash -# Remove module files -sudo rm -rf /usr/lib64/enlightenment/modules/iwd - -# Remove configuration -rm -f ~/.config/enlightenment/module.iwd.cfg -``` - -### Cleanup - -```bash -# Re-enable previous Wi-Fi manager -sudo systemctl enable --now wpa_supplicant -# or -sudo systemctl enable --now NetworkManager -``` - -## Getting Help - -If problems persist: - -1. Check logs: `~/.cache/enlightenment/enlightenment.log` -2. Enable debug mode (see Troubleshooting) -3. Report issue with: - - Distribution and version - - Enlightenment version: `enlightenment -version` - - EFL version: `pkg-config --modversion elementary` - - iwd version: `iwd --version` - - Wireless chipset: `lspci | grep -i network` - - Relevant log output - -## Next Steps - -After successful installation: -- Read [README.md](README.md) for usage instructions -- Configure module settings to your preferences -- Add networks and test connectivity -- Customize theme (advanced users) diff --git a/LICENSE b/LICENSE index 3be5522..fe0e975 100644 --- a/LICENSE +++ b/LICENSE @@ -1,24 +1,33 @@ -BSD 2-Clause License -Copyright (c) 2025, eiwd contributors +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +The above copyright notice and this permission notice shall be included in +all copies of the Software and its Copyright notices. In addition publicly +documented acknowledgment must be given that this software has been used if no +source code of this software is made available publicly. Making the source +available publicly means including the source for this software with the +distribution, or a method to get this software via some reasonable mechanism +(electronic transfer via a network or media) as well as making an offer to +supply the source on request. This Copyright notice serves as an offer to +supply the source on on request as well. Instead of this, supplying +acknowledgments of use of this software in either Copyright notices, Manuals, +Publicity and Marketing documents or any documentation provided with any +product containing this software. This License does not apply to any software +that links to the libraries provided by this software (statically or +dynamically), but only to the software provided. -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +Please see the COPYING-PLAIN for a plain-english explanation of this notice +and its intent. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index cde36cb..0000000 --- a/PLAN.md +++ /dev/null @@ -1,421 +0,0 @@ -# Implementation Plan: eiwd - Enlightenment Wi-Fi Module (iwd Backend) - -## Project Overview - -Create a production-ready Enlightenment module that manages Wi-Fi connections using iwd's D-Bus API, providing a gadget + popup UI following Enlightenment conventions. - -**Current State**: Fresh workspace with only CLAUDE.md PRD -**Target**: Feature parity with econnman Wi-Fi functionality using iwd instead of ConnMan - -## System Context - -- **Enlightenment**: 0.27.1 (Module API version 25) -- **iwd daemon**: Running and accessible via D-Bus (`net.connman.iwd`) -- **Build tools**: Meson, GCC, pkg-config available -- **Libraries**: EFL (eldbus, elementary, ecore, evas, edje, eina) + E headers - -## Implementation Phases - -### Phase 1: Build System & Module Skeleton - -**Goal**: Create loadable .so module with proper build infrastructure - -**Files to Create**: -- `meson.build` (root) - Project definition, dependencies, installation paths -- `src/meson.build` - Source compilation -- `data/meson.build` - Desktop file and theme compilation -- `data/module.desktop` - Module metadata -- `src/e_mod_main.c` - Module entry point (e_modapi_init/shutdown/save) -- `src/e_mod_main.h` - Module structures and config - -**Key Components**: - -1. **Meson root build**: - - Dependencies: enlightenment, eldbus, elementary, ecore, evas, edje, eina - - Installation path: `/usr/lib64/enlightenment/modules/iwd/linux-gnu-x86_64-0.27/module.so` - -2. **Module entry point** (`e_mod_main.c`): - ```c - E_API E_Module_Api e_modapi = { E_MODULE_API_VERSION, "IWD" }; - E_API void *e_modapi_init(E_Module *m); - E_API int e_modapi_shutdown(E_Module *m); - E_API int e_modapi_save(E_Module *m); - ``` - -3. **Config structure** (stored via EET): - - config_version - - auto_connect (bool) - - show_hidden_networks (bool) - - signal_refresh_interval - - preferred_adapter - -**Verification**: Module loads in Enlightenment without crashing - ---- - -### Phase 2: D-Bus Layer (iwd Backend) - -**Goal**: Establish communication with iwd daemon and abstract devices/networks - -**Files to Create**: -- `src/iwd/iwd_dbus.c` + `.h` - D-Bus connection management -- `src/iwd/iwd_device.c` + `.h` - Device abstraction (Station interface) -- `src/iwd/iwd_network.c` + `.h` - Network abstraction -- `src/iwd/iwd_agent.c` + `.h` - Agent for passphrase requests - -**Key Implementations**: - -1. **D-Bus Manager** (`iwd_dbus.c`): - - Connect to system bus `net.connman.iwd` - - Subscribe to ObjectManager signals (InterfacesAdded/Removed) - - Monitor NameOwnerChanged for iwd daemon restart - - Provide signal subscription helpers - -2. **Device Abstraction** (`iwd_device.c`): - ```c - typedef struct _IWD_Device { - char *path, *name, *address; - Eina_Bool powered, scanning; - char *state; // "disconnected", "connecting", "connected" - Eldbus_Proxy *device_proxy, *station_proxy; - } IWD_Device; - ``` - - Operations: scan, disconnect, connect_hidden, get_networks - -3. **Network Abstraction** (`iwd_network.c`): - ```c - typedef struct _IWD_Network { - char *path, *name, *type; // "open", "psk", "8021x" - Eina_Bool known; - int16_t signal_strength; // dBm - Eldbus_Proxy *network_proxy; - } IWD_Network; - ``` - - Operations: connect, forget - -4. **Agent Implementation** (`iwd_agent.c`): - - Register D-Bus service at `/org/enlightenment/eiwd/agent` - - Implement `RequestPassphrase(network_path)` method - - Bridge between iwd requests and UI dialogs - - **Security**: Never log passphrases, clear from memory after sending - -**Verification**: Can list devices, trigger scan, receive PropertyChanged signals - ---- - -### Phase 3: Gadget & Basic UI - -**Goal**: Create shelf icon and popup interface - -**Files to Create**: -- `src/e_mod_gadget.c` - Gadcon provider and icon -- `src/e_mod_popup.c` - Popup window and layout -- `src/ui/wifi_status.c` + `.h` - Current connection widget -- `src/ui/wifi_list.c` + `.h` - Network list widget - -**Key Implementations**: - -1. **Gadcon Provider** (`e_mod_gadget.c`): - ```c - static const E_Gadcon_Client_Class _gc_class = { - GADCON_CLIENT_CLASS_VERSION, "iwd", { ... } - }; - ``` - - Icon states via edje: disconnected, connecting, connected (signal tiers), error - - Click handler: toggle popup - - Tooltip: SSID, signal strength, security type - -2. **Popup Window** (`e_mod_popup.c`): - - Layout: Current Connection + Available Networks + Actions - - Current: SSID, signal, IP, disconnect button - - Networks: sorted by known → signal strength - - Actions: Rescan, Enable/Disable Wi-Fi - -3. **Network List Widget** (`ui/wifi_list.c`): - - Use elm_genlist or e_widget_ilist - - Icons: open/WPA2/WPA3 lock icons - - Sort: known networks first, then by signal - - Click handler: initiate connection - -**Verification**: Gadget appears on shelf, popup opens/closes, networks display - ---- - -### Phase 4: Connection Management - -**Goal**: Complete connection flow including authentication - -**Files to Create**: -- `src/ui/wifi_auth.c` + `.h` - Passphrase dialog -- `src/iwd/iwd_state.c` + `.h` - State machine - -**Connection Flow**: -1. User clicks network in list -2. Check security type (open vs psk vs 8021x) -3. If psk: show auth dialog (`wifi_auth_dialog_show`) -4. Call `network.Connect()` D-Bus method -5. iwd calls agent's `RequestPassphrase` -6. Return passphrase from dialog -7. Monitor `Station.State` PropertyChanged -8. Update UI: connecting → connected - -**State Machine** (`iwd_state.c`): -```c -typedef enum { - IWD_STATE_OFF, // Powered = false - IWD_STATE_IDLE, // Powered = true, disconnected - IWD_STATE_SCANNING, - IWD_STATE_CONNECTING, - IWD_STATE_CONNECTED, - IWD_STATE_ERROR // iwd not running -} IWD_State; -``` - -**Known Networks**: -- List via KnownNetwork interface -- Operations: Forget, Set AutoConnect -- UI: star icon for known, context menu - -**Verification**: Connect to open/WPA2 networks, disconnect, forget network - ---- - -### Phase 5: Advanced Features - -**Goal**: Handle edge cases and advanced scenarios - -**Implementations**: - -1. **Hidden Networks**: - - Add "Connect to Hidden Network" button - - Call `Station.ConnectHiddenNetwork(ssid)` - -2. **Multiple Adapters**: - - Monitor all `/net/connman/iwd/[0-9]+` paths - - UI: dropdown/tabs if multiple devices - - Config: preferred adapter selection - -3. **Daemon Restart Handling**: - - Monitor NameOwnerChanged for `net.connman.iwd` - - On restart: re-query ObjectManager, re-register agent, recreate proxies - - Set error state while daemon down - -4. **Polkit Integration**: - - Detect `NotAuthorized` errors - - Show user-friendly permission error dialog - - Document required polkit rules (don't auto-install) - -**Error Handling**: -- iwd not running → error state icon -- No wireless device → graceful message -- Permission denied → polkit error dialog -- Auth failure → clear error message (wrong password) - -**Verification**: Handle iwd restart, multiple adapters, polkit errors - ---- - -### Phase 6: Theme & Polish - -**Goal**: Professional UI appearance and internationalization - -**Files to Create**: -- `data/theme.edc` - Edje theme definition -- `data/icons/*.svg` - Icon source files -- `po/POTFILES.in` - i18n file list -- `po/eiwd.pot` - Translation template -- `src/e_mod_config.c` - Configuration dialog - -**Theme Groups** (`theme.edc`): -- `e/modules/iwd/main` - Gadget icon -- `e/modules/iwd/signal/{0,25,50,75,100}` - Signal strength icons - -**Configuration Dialog** (`e_mod_config.c`): -- Auto-connect to known networks: checkbox -- Show hidden networks: checkbox -- Signal refresh interval: slider (1-60s) -- Preferred adapter: dropdown - -**i18n**: -- Mark strings with `D_(str)` macro (dgettext) -- Meson gettext integration - -**Verification**: Theme scales properly, config saves, translations work - ---- - -### Phase 7: Testing & Documentation - -**Testing**: -- Unit tests: SSID parsing, signal conversion, config serialization -- Memory leak check: Valgrind during connect/disconnect cycles -- Manual checklist: - - [ ] Module loads without errors - - [ ] Scan, connect, disconnect work - - [ ] Wrong password shows error - - [ ] Known network auto-connect - - [ ] iwd restart recovery - - [ ] Suspend/resume handling - - [ ] No device graceful degradation - -**Documentation** (`README.md`, `INSTALL.md`): -- Overview and features -- Dependencies -- Building with Meson -- Installation paths -- iwd setup requirements -- Polkit configuration -- Troubleshooting - -**Verification**: All tests pass, documentation complete - ---- - -### Phase 8: Packaging & Distribution - -**Packaging**: -- Arch Linux PKGBUILD -- Gentoo ebuild -- Generic tarball - -**Installation**: -```bash -meson setup build -ninja -C build -sudo ninja -C build install -``` - -Module location: `/usr/lib64/enlightenment/modules/iwd/` - -**Verification**: Clean install works, module appears in E module list - ---- - -## Directory Structure - -``` -/home/nemunaire/workspace/eiwd/ -├── meson.build # Root build config -├── meson_options.txt -├── README.md -├── INSTALL.md -├── LICENSE -├── data/ -│ ├── meson.build -│ ├── module.desktop # Module metadata -│ ├── theme.edc # Edje theme -│ └── icons/ # SVG/PNG icons -├── po/ # i18n -│ ├── POTFILES.in -│ └── eiwd.pot -├── src/ -│ ├── meson.build -│ ├── e_mod_main.c # Module entry point -│ ├── e_mod_main.h -│ ├── e_mod_config.c # Config dialog -│ ├── e_mod_gadget.c # Shelf icon -│ ├── e_mod_popup.c # Popup window -│ ├── iwd/ -│ │ ├── iwd_dbus.c # D-Bus connection -│ │ ├── iwd_dbus.h -│ │ ├── iwd_device.c # Device abstraction -│ │ ├── iwd_device.h -│ │ ├── iwd_network.c # Network abstraction -│ │ ├── iwd_network.h -│ │ ├── iwd_agent.c # Agent implementation -│ │ ├── iwd_agent.h -│ │ ├── iwd_state.c # State machine -│ │ └── iwd_state.h -│ └── ui/ -│ ├── wifi_status.c # Connection status widget -│ ├── wifi_status.h -│ ├── wifi_list.c # Network list widget -│ ├── wifi_list.h -│ ├── wifi_auth.c # Passphrase dialog -│ └── wifi_auth.h -└── tests/ - ├── meson.build - └── test_network.c -``` - ---- - -## Critical Files (Implementation Order) - -1. **`meson.build`** - Build system foundation -2. **`src/e_mod_main.c`** - Module lifecycle (init/shutdown/save) -3. **`src/iwd/iwd_dbus.c`** - D-Bus connection to iwd -4. **`src/iwd/iwd_agent.c`** - Passphrase handling (essential for secured networks) -5. **`src/e_mod_gadget.c`** - Primary user interface (shelf icon) - ---- - -## Key Technical Decisions - -**Build System**: Meson (modern, used by newer E modules) -**UI Framework**: Elementary widgets (EFL/Enlightenment standard) -**D-Bus Library**: eldbus (EFL integration, async) -**State Management**: Signal-driven (no polling) -**Security**: Never log passphrases, rely on iwd for credential storage - ---- - -## Performance Targets - -- Startup: < 100ms -- Popup open: < 200ms -- Network scan: < 2s -- Memory footprint: < 5 MB -- No periodic polling (signal-driven only) - ---- - -## Dependencies - -**Build**: -- meson >= 0.56 -- ninja -- gcc/clang -- pkg-config -- edje_cc - -**Runtime**: -- enlightenment >= 0.25 -- efl (elementary, eldbus, ecore, evas, edje, eina) -- iwd >= 1.0 -- dbus - -**Optional**: -- polkit (permissions management) - ---- - -## Security Considerations - -1. **Never log passphrases** - No debug output of credentials -2. **Clear sensitive data** - memset passphrases after use -3. **D-Bus only** - No plaintext credential storage in module -4. **Polkit enforcement** - Respect system authorization policies -5. **Validate D-Bus params** - Don't trust all incoming data - ---- - -## Known Limitations - -- No VPN support (out of scope per PRD) -- No ethernet management (iwd is Wi-Fi only) -- Basic EAP UI (username/password only, no advanced cert config) -- No WPS support in initial version - ---- - -## Success Criteria - -- Module loads and appears in Enlightenment module list -- Can scan for networks and display them sorted by known + signal -- Can connect to open and WPA2/WPA3 networks with passphrase -- Can disconnect and forget networks -- Handles iwd daemon restart gracefully -- No UI freezes during scan/connect operations -- Memory leak free (Valgrind clean) -- Feature parity with econnman Wi-Fi functionality diff --git a/README.md b/README.md index f22e75d..a4afce7 100644 --- a/README.md +++ b/README.md @@ -1,225 +1,161 @@ -# eiwd - Enlightenment Wi-Fi Module (iwd Backend) +# e_iwd — Enlightenment Wi-Fi module (iwd backend) -A native Enlightenment module for managing Wi-Fi connections using Intel Wireless Daemon (iwd) as the backend. +A native [Enlightenment](https://www.enlightenment.org/) gadget that +manages wireless connections through [iwd](https://iwd.wiki.kernel.org/), +the Intel Wireless Daemon. No NetworkManager, no ConnMan, no shelling +out to `iwctl` — everything goes over iwd's D‑Bus API. -## Overview - -**eiwd** provides seamless Wi-Fi management directly within the Enlightenment desktop environment without requiring NetworkManager or ConnMan. It uses iwd's D-Bus API for fast, lightweight, and reliable wireless networking. +It is roughly the iwd-only equivalent of `econnman`. ## Features -### Core Functionality -- **Device Management**: Automatic detection of wireless adapters -- **Network Discovery**: Fast scanning with signal strength indicators -- **Connection Management**: Connect/disconnect with one click -- **Known Networks**: Automatic connection to saved networks -- **Hidden Networks**: Support for connecting to non-broadcast SSIDs -- **Security**: WPA2/WPA3-PSK authentication with secure passphrase handling - -### User Interface -- **Shelf Gadget**: Compact icon showing connection status -- **Visual Feedback**: Color-coded states (disconnected, connecting, connected, error) -- **Popup Menu**: Quick access to available networks and actions -- **Configuration Dialog**: Customizable settings and preferences -- **Theme Support**: Full Edje theme integration with signal strength display - -### Advanced Features -- **Daemon Recovery**: Automatic reconnection when iwd restarts -- **Multi-Adapter Support**: Detection and management of multiple wireless devices -- **State Machine**: Robust connection state tracking -- **Error Handling**: User-friendly error messages with troubleshooting hints -- **Polkit Integration**: Respects system authorization policies - -## Requirements - -### Build Dependencies -- `enlightenment` >= 0.25 -- `efl` (Elementary, Eldbus, Ecore, Evas, Edje, Eina) >= 1.26 -- `meson` >= 0.56 -- `ninja` build tool -- `gcc` or `clang` compiler -- `pkg-config` -- `edje_cc` (EFL development tools) - -### Runtime Dependencies -- `enlightenment` >= 0.25 -- `efl` runtime libraries -- `iwd` >= 1.0 -- `dbus` system bus - -### Optional -- `polkit` for fine-grained permission management -- `gettext` for internationalization support - -## Building - -```bash -# Clone repository -git clone eiwd -cd eiwd - -# Configure build -meson setup build - -# Compile -ninja -C build - -# Install (as root) -sudo ninja -C build install -``` - -### Build Options - -```bash -# Disable internationalization -meson setup build -Dnls=false - -# Custom installation prefix -meson setup build --prefix=/usr/local -``` - -## Installation - -The module is installed to: -``` -/usr/lib64/enlightenment/modules/iwd/linux-x86_64-0.27/ -├── module.so # Main module binary -└── e-module-iwd.edj # Theme file -``` - -After installation: -1. Open Enlightenment Settings → Modules -2. Find "IWD Wi-Fi" in the list -3. Click "Load" to enable the module -4. Add the gadget to your shelf via shelf settings - -## Configuration - -### Module Settings - -Access via right-click on the gadget or Settings → Modules → IWD → Configure: - -- **Auto-connect**: Automatically connect to known networks -- **Show hidden networks**: Display option to connect to hidden SSIDs -- **Signal refresh interval**: Update frequency (1-60 seconds) -- **Preferred adapter**: Select default wireless device (multi-adapter systems) - -### iwd Setup - -Ensure iwd is running and enabled: - -```bash -# Enable and start iwd -sudo systemctl enable --now iwd - -# Check status -sudo systemctl status iwd -``` - -### Polkit Rules - -For non-root users to manage Wi-Fi, create `/etc/polkit-1/rules.d/50-iwd.rules`: - -```javascript -polkit.addRule(function(action, subject) { - if (action.id.indexOf("net.connman.iwd.") == 0 && - subject.isInGroup("wheel")) { - return polkit.Result.YES; - } -}); -``` - -Adjust the group (`wheel`, `network`, etc.) according to your distribution. - -## Usage - -### Basic Operations - -1. **Scan for networks**: Click "Rescan" in the popup -2. **Connect to a network**: Click the network name, enter passphrase if required -3. **Disconnect**: Click "Disconnect" in the popup -4. **Forget network**: Right-click network → Forget (removes saved credentials) -5. **Hidden network**: Click "Hidden..." button, enter SSID and passphrase - -### Status Indicator - -The gadget icon shows current state: -- **Gray**: Disconnected or no wireless device -- **Orange/Yellow**: Connecting to network -- **Green**: Connected successfully -- **Red**: Error (iwd not running or permission denied) - -### Troubleshooting - -See [INSTALL.md](INSTALL.md#troubleshooting) for common issues and solutions. +- **Shelf gadget** with a signal-tier icon (off / acquiring / weak…excellent) + and a tooltip showing the current SSID, security type, and signal level. +- **Popup network browser** (left-click the gadget): + - status line: disabled / disconnected / scanning / connecting / connected + - sorted network list — connected first, then known networks, then by + signal strength; long SSIDs are truncated to keep the popup tidy + - per-row signal bars and security tag (`open` / `WPA` / `WEP` / `802.1X`) + - **Connect** by clicking a row, **Forget** (`✕`) on known networks + - **Rescan**, **Enable / Disable** Wi‑Fi + - **Disconnect** button visible while connected + - **Hidden…** button to join a non-broadcasting SSID +- **Authentication agent** registered with iwd: + - passphrase prompt for new protected networks (modal dialog window) + - cancel-on-`Agent.Cancel` so iwd-initiated cancellations close the + open prompt cleanly + - polite stubs for `RequestUserNameAndPassword`, `RequestUserPassword` + and `RequestPrivateKeyPassphrase` so iwd doesn't unregister us when + it tries them on EAP networks +- **Settings dialog** (right-click the gadget → Settings): + - auto-connect to known networks + - show hidden networks + - signal refresh interval + - preferred wireless adapter +- **Robust to iwd lifecycle**: tracks `net.connman.iwd` name owner, + re-binds objects on restart, clears state on departure. ## Architecture -### Components - ``` -eiwd/ +e_iwd/ ├── src/ -│ ├── e_mod_main.c # Module entry point -│ ├── e_mod_config.c # Configuration dialog -│ ├── e_mod_gadget.c # Shelf gadget icon -│ ├── e_mod_popup.c # Network list popup -│ ├── iwd/ # D-Bus backend -│ │ ├── iwd_dbus.c # Connection management -│ │ ├── iwd_device.c # Device abstraction -│ │ ├── iwd_network.c # Network abstraction -│ │ ├── iwd_agent.c # Authentication agent -│ │ └── iwd_state.c # State machine -│ └── ui/ # UI dialogs -│ ├── wifi_auth.c # Passphrase input -│ └── wifi_hidden.c # Hidden network dialog -└── data/ - ├── theme.edc # Edje theme - └── module.desktop # Module metadata +│ ├── e_mod_main.c module init/shutdown +│ ├── e_mod_gadget.c gadcon provider, icon + tooltip + menu +│ ├── e_mod_popup.c network list popup +│ ├── e_mod_config.c persistent settings + E_Config_Dialog +│ ├── iwd/ +│ │ ├── iwd_dbus.c system bus, name owner, ObjectManager +│ │ ├── iwd_manager.c top-level state aggregator + listeners +│ │ ├── iwd_adapter.c net.connman.iwd.Adapter (Powered) +│ │ ├── iwd_device.c Device + Station, scan/connect/disconnect, +│ │ │ GetOrderedNetworks → signal strength +│ │ ├── iwd_network.c Network.Connect, KnownNetwork.Forget +│ │ ├── iwd_agent.c net.connman.iwd.Agent (passphrase, cancel) +│ │ └── iwd_props.c a{sv} parsing helpers +│ └── ui/ +│ ├── wifi_auth.c passphrase dialog (floating elm_win) +│ └── wifi_hidden.c hidden-network SSID + passphrase dialog +└── meson.build ``` -### D-Bus Integration +Data flow: -Communicates with iwd via system bus (`net.connman.iwd`): -- `ObjectManager` for device/network discovery -- `Device` and `Station` interfaces for wireless operations -- `Network` interface for connection management -- `Agent` registration for passphrase requests +``` +iwd (D-Bus) ──► iwd_dbus ──► iwd_manager ──► iwd_device / iwd_network + │ + ├──► listeners (gadget, popup) + └──► Iwd_Agent ──► UI passphrase prompt +``` -## Performance +The module uses **Eldbus** for all bus traffic and **Elementary** for +its widgets. Everything is async — no blocking calls on the UI thread. -- **Startup time**: < 100ms -- **Memory footprint**: ~5 MB -- **No polling**: Event-driven updates via D-Bus signals -- **Theme compilation**: Optimized Edje binary format +## Building + +Dependencies (development headers): + +- Enlightenment ≥ 0.25 (tested against 0.27) +- EFL ≥ 1.26 (Eldbus, Elementary, Edje, Ecore, Eina) +- meson + ninja +- a running `iwd` ≥ 1.0 (runtime, not build-time) + +Build and install: + +```sh +meson setup build +ninja -C build +sudo ninja -C build install +``` + +The module is installed to +`/enlightenment/modules/iwd//module.so`. The +`module_arch` and `libdir` are pulled from Enlightenment's pkg-config +file, so the install path matches whatever your distro packages. + +Once installed, enable it from **Settings → Modules → Extensions → +iwd**, then add the gadget to a shelf or the desktop via +**Settings → Gadgets**. + +## Runtime requirements + +- `iwd` running as a system service (`systemctl enable --now iwd`). +- Your user must be allowed to talk to `net.connman.iwd` on the system + bus. On most distros this means being in the `network` group, or + having a polkit rule for the `net.connman.iwd` interfaces. The module + degrades gracefully when permissions are missing — you'll just see an + empty list. +- A wireless adapter managed by iwd (i.e. not claimed by + NetworkManager / wpa_supplicant). + +## Usage + +| Action | How | +|---|---| +| Open the network list | Left-click the gadget | +| Open settings | Right-click the gadget → Settings | +| Connect to a known network | Click its row in the list | +| Connect to a new protected network | Click its row, enter the passphrase in the dialog | +| Forget a known network | Click the `✕` button on its row | +| Disconnect | Click **Disconnect** in the popup (visible while connected) | +| Join a hidden SSID | Click **Hidden…**, enter SSID and (optional) passphrase | +| Rescan | Click **Rescan** | +| Disable / enable Wi-Fi | Click **Disable** / **Enable** in the popup | + +Passphrases are sent straight to iwd over D-Bus. They are never logged, +never written to the module config, and are zeroed in memory once the +dialog closes. + +## Configuration + +Settings are persisted via Enlightenment's standard config system as +`module.iwd` (an `eet` file under your E config profile, e.g. +`~/.e/e/config//module.iwd.cfg`). Fields: + +| Field | Default | Meaning | +|---|---|---| +| `auto_connect` | on | Let iwd auto-connect to known networks | +| `show_hidden` | off | Reveal hidden networks in the list | +| `refresh_interval` | 5 | Signal-strength refresh interval (seconds) | +| `preferred_adapter` | — | Preferred wireless adapter (blank = auto) | + +## Status + +Phases 0–4 of the project are complete and the module builds cleanly +against EFL 1.28 / Enlightenment 0.27. Phase 5 (robustness) and Phase 6 +(packaging) are partially landed. + +Known gaps: + +- No custom theme `edj` — the gadget uses freedesktop icon names from + the active icon theme. +- No suspend / resume integration. +- No EAP UI (RequestUserName / RequestPrivateKey are stubbed to + refuse). +- Multi-adapter UX is auto-select-first; the preferred-adapter setting + is plumbed but not yet honored by the manager. +- Not yet tested by valgrind for leaks. ## License -[Specify your license here - e.g., BSD, GPL, MIT] - -## Contributing - -Contributions welcome! Please: -1. Fork the repository -2. Create a feature branch -3. Test thoroughly (see Testing section) -4. Submit a pull request - -## Support - -- **Issues**: Report bugs via GitHub Issues -- **Documentation**: See [INSTALL.md](INSTALL.md) for detailed setup -- **IRC/Matrix**: [Specify chat channels if available] - -## Credits - -Developed with Claude Code - https://claude.com/claude-code - -Based on iwd (Intel Wireless Daemon) by Intel Corporation -Built for the Enlightenment desktop environment - -## See Also - -- [iwd documentation](https://iwd.wiki.kernel.org/) -- [Enlightenment documentation](https://www.enlightenment.org/docs) -- [EFL API reference](https://docs.enlightenment.org/api/efl/start) +MIT-style, matching Enlightenment and EFL. See `LICENSE`. diff --git a/TESTING.md b/TESTING.md deleted file mode 100644 index f4ed9e2..0000000 --- a/TESTING.md +++ /dev/null @@ -1,448 +0,0 @@ -# Testing Checklist - eiwd - -Manual testing checklist for verifying eiwd functionality. - -## Pre-Testing Setup - -### Environment Verification - -- [ ] iwd service is running: `systemctl status iwd` -- [ ] Wireless device is detected: `iwctl device list` -- [ ] D-Bus connection works: `dbus-send --system --dest=net.connman.iwd --print-reply / org.freedesktop.DBus.Introspectable.Introspect` -- [ ] No conflicting services (wpa_supplicant, NetworkManager Wi-Fi) -- [ ] User has proper permissions (polkit rules configured) - -### Build Verification - -```bash -# Clean build -rm -rf build -meson setup build -ninja -C build - -# Verify artifacts -ls -lh build/src/module.so # Should be ~230KB -ls -lh build/data/e-module-iwd.edj # Should be ~10-12KB - -# Check for warnings -ninja -C build 2>&1 | grep -i warning -``` - -Expected: No critical warnings, module compiles successfully. - -## Module Loading Tests - -### Basic Loading - -- [ ] Module loads without errors: `enlightenment_remote -module-load iwd` -- [ ] Module appears in Settings → Modules -- [ ] Module shows "Running" status -- [ ] No errors in `~/.cache/enlightenment/enlightenment.log` -- [ ] Gadget can be added to shelf - -### Initialization - -- [ ] Gadget icon appears on shelf after adding -- [ ] Icon shows appropriate initial state (gray/disconnected or green/connected) -- [ ] Tooltip displays correctly on hover -- [ ] No crashes or freezes after loading - -## UI Interaction Tests - -### Gadget - -- [ ] Left-click opens popup menu -- [ ] Icon color reflects current state: - - Gray: Disconnected - - Orange/Yellow: Connecting - - Green: Connected - - Red: Error (iwd not running) -- [ ] Tooltip shows correct information: - - Current SSID (if connected) - - Signal strength - - Connection status -- [ ] Multiple clicks toggle popup open/close without issues - -### Popup Menu - -- [ ] Popup appears at correct position near gadget -- [ ] Current connection section shows: - - Connected SSID (if applicable) - - Signal strength - - Disconnect button (when connected) -- [ ] Available networks list displays: - - Network SSIDs - - Security type indicators (lock icons) - - Signal strength - - Known networks marked/sorted appropriately -- [ ] Action buttons present: - - "Rescan" button - - "Hidden..." button (if enabled in config) - - "Enable/Disable Wi-Fi" button -- [ ] Popup stays open when interacting with widgets -- [ ] Clicking outside popup closes it - -### Configuration Dialog - -- [ ] Config dialog opens from module settings -- [ ] All settings visible: - - Auto-connect checkbox - - Show hidden networks checkbox - - Signal refresh slider - - Adapter selection (if multiple devices) -- [ ] Changes save correctly -- [ ] Applied settings persist after restart -- [ ] Dialog can be closed with OK/Cancel -- [ ] Multiple opens don't create duplicate dialogs - -## Network Operations Tests - -### Scanning - -- [ ] Manual scan via "Rescan" button works -- [ ] Networks appear in list after scan -- [ ] List updates showing new networks -- [ ] Signal strength values reasonable (-30 to -90 dBm) -- [ ] Duplicate networks not shown -- [ ] Scan doesn't freeze UI -- [ ] Periodic auto-refresh works (based on config interval) - -### Connecting to Open Network - -- [ ] Click on open network initiates connection -- [ ] Icon changes to "connecting" state (orange) -- [ ] No passphrase dialog appears -- [ ] Connection succeeds within 10 seconds -- [ ] Icon changes to "connected" state (green) -- [ ] Tooltip shows connected SSID -- [ ] Current connection section updated in popup - -### Connecting to Secured Network (WPA2/WPA3) - -- [ ] Click on secured network opens passphrase dialog -- [ ] Dialog shows network name -- [ ] Password field is hidden (dots/asterisks) -- [ ] Entering correct passphrase connects successfully -- [ ] Wrong passphrase shows error message -- [ ] Cancel button closes dialog without connecting -- [ ] Connection state updates correctly -- [ ] Passphrase is not logged to any logs - -### Disconnecting - -- [ ] "Disconnect" button appears when connected -- [ ] Clicking disconnect terminates connection -- [ ] Icon changes to disconnected state -- [ ] Current connection section clears -- [ ] No error messages on clean disconnect - -### Forgetting Network - -- [ ] Known networks can be forgotten (via context menu or dedicated UI) -- [ ] Forgetting removes from known list -- [ ] Network still appears in scan results (as unknown) -- [ ] Auto-connect disabled after forgetting - -### Hidden Networks - -- [ ] "Hidden..." button opens dialog -- [ ] Can enter SSID manually -- [ ] Passphrase field available for secured networks -- [ ] Connection attempt works correctly -- [ ] Error handling for non-existent SSID -- [ ] Successfully connected hidden network saved - -## State Management Tests - -### Connection States - -- [ ] OFF state: Wi-Fi powered off, icon gray -- [ ] IDLE state: Wi-Fi on but disconnected, icon gray -- [ ] SCANNING state: Scan in progress -- [ ] CONNECTING state: Connection attempt, icon orange -- [ ] CONNECTED state: Active connection, icon green -- [ ] ERROR state: iwd not running, icon red - -### Transitions - -- [ ] Disconnected → Connecting → Connected works smoothly -- [ ] Connected → Disconnecting → Disconnected works smoothly -- [ ] Error → Idle when iwd starts -- [ ] UI updates reflect state changes within 1-2 seconds - -## Advanced Features Tests - -### Multiple Adapters - -If system has multiple wireless devices: -- [ ] Both devices detected -- [ ] Can select preferred adapter in config -- [ ] Switching adapters works correctly -- [ ] Each adapter shows separate networks - -### iwd Daemon Restart - -```bash -# While module is running and connected -sudo systemctl restart iwd -``` - -- [ ] Gadget shows error state (red) when iwd stops -- [ ] Error dialog appears notifying daemon stopped -- [ ] Automatic reconnection when iwd restarts -- [ ] Agent re-registers successfully -- [ ] Can reconnect to networks after restart -- [ ] No module crashes - -### Auto-Connect - -- [ ] Enable auto-connect in config -- [ ] Disconnect from current network -- [ ] Module reconnects automatically to known network -- [ ] Disable auto-connect prevents automatic connection -- [ ] Auto-connect works after system restart - -### Polkit Permission Errors - -```bash -# Temporarily break polkit rules -sudo mv /etc/polkit-1/rules.d/50-iwd.rules /tmp/ -``` - -- [ ] Permission denied error shows user-friendly message -- [ ] Error dialog suggests polkit configuration -- [ ] Module doesn't crash -- [ ] Restoring rules allows operations again - -## Error Handling Tests - -### No Wireless Device - -```bash -# Simulate by blocking with rfkill -sudo rfkill block wifi -``` - -- [ ] Gadget shows appropriate state -- [ ] Error message clear to user -- [ ] Unblocking device recovers gracefully - -### Wrong Password - -- [ ] Entering wrong WPA password shows error -- [ ] Error message is helpful (not just "Failed") -- [ ] Can retry with different password -- [ ] Multiple failures don't crash module - -### Network Out of Range - -- [ ] Attempting to connect to weak/distant network -- [ ] Timeout handled gracefully -- [ ] Error message explains problem - -### iwd Not Running - -```bash -sudo systemctl stop iwd -``` - -- [ ] Gadget immediately shows error state -- [ ] User-friendly error dialog -- [ ] Instructions to start iwd service -- [ ] Module continues running (no crash) - -## Performance Tests - -### Responsiveness - -- [ ] Popup opens within 200ms of click -- [ ] Network list populates within 500ms -- [ ] UI remains responsive during scan -- [ ] No freezing during connect operations -- [ ] Configuration dialog opens quickly - -### Resource Usage - -```bash -# Check memory usage -ps aux | grep enlightenment -``` - -- [ ] Module uses < 10 MB RAM -- [ ] No memory leaks after multiple connect/disconnect cycles -- [ ] CPU usage < 1% when idle -- [ ] CPU spike during scan acceptable (< 3 seconds) - -### Stability - -- [ ] No crashes after 10 connect/disconnect cycles -- [ ] Module stable for 1+ hour of operation -- [ ] Theme rendering consistent -- [ ] No visual glitches in popup - -## Theme Tests - -### Visual Appearance - -- [ ] Theme file loads successfully -- [ ] Icon appearance matches theme groups -- [ ] Colors appropriate for each state -- [ ] Signal strength indicator displays -- [ ] Theme scales properly with shelf size -- [ ] Theme works in different Enlightenment themes - -### Fallback Behavior - -```bash -# Rename theme to simulate missing -sudo mv /usr/lib*/enlightenment/modules/iwd/*/e-module-iwd.edj \ - /usr/lib*/enlightenment/modules/iwd/*/e-module-iwd.edj.bak -``` - -- [ ] Module still functions with colored rectangles -- [ ] No crashes due to missing theme -- [ ] Warning logged about missing theme -- [ ] Restoring theme works after module reload - -## Integration Tests - -### Suspend/Resume - -```bash -# Trigger system suspend -systemctl suspend -``` - -After resume: -- [ ] Module still functional -- [ ] Reconnects to previous network -- [ ] No errors in logs - -### Multiple Instances - -- [ ] Can add multiple gadgets to different shelves -- [ ] Each instance updates independently -- [ ] Removing one doesn't affect others -- [ ] All instances show same connection state - -### Configuration Persistence - -- [ ] Settings saved to `~/.config/enlightenment/module.iwd.cfg` -- [ ] Settings persist across Enlightenment restarts -- [ ] Settings persist across system reboots -- [ ] Corrupted config file handled gracefully - -## Regression Tests - -After code changes, verify: - -### Core Functionality - -- [ ] Module loads -- [ ] Can scan networks -- [ ] Can connect to WPA2 network -- [ ] Can disconnect -- [ ] Configuration dialog works - -### No New Issues - -- [ ] No new compiler warnings -- [ ] No new memory leaks (valgrind) -- [ ] No new crashes in logs -- [ ] Documentation still accurate - -## Memory Leak Testing - -```bash -# Run Enlightenment under Valgrind (slow!) -valgrind --leak-check=full \ - --track-origins=yes \ - --log-file=valgrind.log \ - enlightenment_start - -# Perform operations: -# - Load module -# - Scan networks -# - Connect/disconnect 5 times -# - Open config dialog -# - Unload module - -# Check results -grep "definitely lost" valgrind.log -grep "indirectly lost" valgrind.log -``` - -Expected: No memory leaks from eiwd code (EFL/E leaks may exist). - -## Cleanup After Testing - -```bash -# Restore any changed files -sudo systemctl start iwd -sudo rfkill unblock wifi - -# Restore polkit rules if moved -sudo mv /tmp/50-iwd.rules /etc/polkit-1/rules.d/ - -# Restore theme if renamed -# ... - -# Clear test networks -sudo rm /var/lib/iwd/TestNetwork.psk -``` - -## Test Report Template - -``` -## Test Report - eiwd v0.1.0 - -**Date**: YYYY-MM-DD -**Tester**: Name -**System**: Distribution, Kernel version -**E Version**: 0.27.x -**iwd Version**: X.XX - -### Summary -- Tests Passed: XX/YY -- Tests Failed: Z -- Critical Issues: N - -### Failed Tests -1. Test name: Description of failure -2. ... - -### Notes -- Any observations -- Performance metrics -- Suggestions - -### Conclusion -[Pass/Fail/Conditional Pass] -``` - -## Automated Testing (Future) - -Placeholder for unit tests: - -```c -// tests/test_network.c -// Basic functionality tests - -#include -#include "iwd_network.h" - -START_TEST(test_network_creation) -{ - IWD_Network *net = iwd_network_new("/test/path"); - ck_assert_ptr_nonnull(net); - iwd_network_free(net); -} -END_TEST - -// More tests... -``` - -Build and run: -```bash -meson test -C build -``` diff --git a/data/e-module-iwd.edc b/data/e-module-iwd.edc new file mode 100644 index 0000000..0c3f4c6 --- /dev/null +++ b/data/e-module-iwd.edc @@ -0,0 +1,36 @@ +/* Minimal theme for the iwd module gadget. + * + * Two groups are exported: + * - "icon" : used by the module loader / gadget chooser preview + * - "e/modules/iwd/main" : the gadget itself, with a single swallow + * "e.swallow.content" the C code drops the icon in. + */ + +collections { + group { name: "icon"; + min: 24 24; + max: 256 256; + parts { + part { name: "icon"; type: SWALLOW; + description { state: "default" 0.0; + aspect: 1.0 1.0; + aspect_preference: BOTH; + } + } + } + } + + group { name: "e/modules/iwd/main"; + min: 4 4; + parts { + part { name: "e.swallow.content"; type: SWALLOW; + description { state: "default" 0.0; + rel1.relative: 0.0 0.0; + rel2.relative: 1.0 1.0; + aspect: 1.0 1.0; + aspect_preference: BOTH; + } + } + } + } +} diff --git a/data/meson.build b/data/meson.build index e41d4fa..1d0e0eb 100644 --- a/data/meson.build +++ b/data/meson.build @@ -1,18 +1,11 @@ -# Install desktop file -install_data('module.desktop', - install_dir: dir_module -) +install_data('module.desktop', install_dir : module_dir) -# Compile theme -edje_cc = find_program('edje_cc', required: false) -if edje_cc.found() - custom_target('theme', - input: 'theme.edc', - output: 'e-module-iwd.edj', - command: [edje_cc, '@INPUT@', '@OUTPUT@'], - install: true, - install_dir: dir_module - ) -else - warning('edje_cc not found, theme will not be compiled') -endif +edje_cc = find_program('edje_cc') + +iwd_theme = custom_target('e-module-iwd.edj', + input : 'e-module-iwd.edc', + output : 'e-module-iwd.edj', + command : [edje_cc, '-id', meson.current_source_dir(), '@INPUT@', '@OUTPUT@'], + install : true, + install_dir : module_dir, +) diff --git a/data/module.desktop b/data/module.desktop index cee7de4..bb7c103 100644 --- a/data/module.desktop +++ b/data/module.desktop @@ -1,8 +1,6 @@ [Desktop Entry] Type=Link -Name=IWD Wi-Fi -Name[en]=IWD Wi-Fi Manager -Comment=Manage Wi-Fi connections using iwd -Comment[en]=Control Wi-Fi networks using Intel Wireless Daemon +Name=iwd Icon=e-module-iwd -X-Enlightenment-ModuleType=system +Comment=Wi-Fi management via iwd +X-Enlightenment-ModuleType=utils diff --git a/data/theme.edc b/data/theme.edc deleted file mode 100644 index f8989c5..0000000 --- a/data/theme.edc +++ /dev/null @@ -1,188 +0,0 @@ -/* IWD Module Theme */ - -collections { - /* Main gadget icon - base group */ - group { - name: "e/modules/iwd/main"; - min: 16 16; - max: 128 128; - - parts { - /* Background */ - part { - name: "bg"; - type: RECT; - description { - state: "default" 0.0; - color: 0 0 0 0; /* Transparent */ - } - } - - /* Wi-Fi icon base */ - part { - name: "icon"; - type: RECT; - description { - state: "default" 0.0; - rel1.relative: 0.1 0.1; - rel2.relative: 0.9 0.9; - color: 128 128 128 255; /* Gray - disconnected */ - } - description { - state: "connected" 0.0; - inherit: "default" 0.0; - color: 0 200 0 255; /* Green - connected */ - } - description { - state: "connecting" 0.0; - inherit: "default" 0.0; - color: 255 165 0 255; /* Orange - connecting */ - } - description { - state: "error" 0.0; - inherit: "default" 0.0; - color: 255 0 0 255; /* Red - error */ - } - } - - /* Signal strength indicator */ - part { - name: "signal"; - type: RECT; - description { - state: "default" 0.0; - visible: 0; - rel1.relative: 0.7 0.7; - rel2.relative: 0.95 0.95; - color: 255 255 255 200; - } - description { - state: "visible" 0.0; - inherit: "default" 0.0; - visible: 1; - } - } - } - - programs { - program { - name: "go_connected"; - signal: "e,state,connected"; - source: "e"; - action: STATE_SET "connected" 0.0; - target: "icon"; - } - program { - name: "go_connecting"; - signal: "e,state,connecting"; - source: "e"; - action: STATE_SET "connecting" 0.0; - target: "icon"; - } - program { - name: "go_disconnected"; - signal: "e,state,disconnected"; - source: "e"; - action: STATE_SET "default" 0.0; - target: "icon"; - } - program { - name: "go_error"; - signal: "e,state,error"; - source: "e"; - action: STATE_SET "error" 0.0; - target: "icon"; - } - program { - name: "signal_show"; - signal: "e,signal,show"; - source: "e"; - action: STATE_SET "visible" 0.0; - target: "signal"; - } - program { - name: "signal_hide"; - signal: "e,signal,hide"; - source: "e"; - action: STATE_SET "default" 0.0; - target: "signal"; - } - } - } - - /* Signal strength icons */ - group { - name: "e/modules/iwd/signal/0"; - min: 16 16; - parts { - part { - name: "base"; - type: RECT; - description { - state: "default" 0.0; - color: 64 64 64 255; - } - } - } - } - - group { - name: "e/modules/iwd/signal/25"; - min: 16 16; - parts { - part { - name: "base"; - type: RECT; - description { - state: "default" 0.0; - color: 255 64 64 255; /* Red - weak */ - } - } - } - } - - group { - name: "e/modules/iwd/signal/50"; - min: 16 16; - parts { - part { - name: "base"; - type: RECT; - description { - state: "default" 0.0; - color: 255 165 0 255; /* Orange - fair */ - } - } - } - } - - group { - name: "e/modules/iwd/signal/75"; - min: 16 16; - parts { - part { - name: "base"; - type: RECT; - description { - state: "default" 0.0; - color: 200 200 0 255; /* Yellow - good */ - } - } - } - } - - group { - name: "e/modules/iwd/signal/100"; - min: 16 16; - parts { - part { - name: "base"; - type: RECT; - description { - state: "default" 0.0; - color: 0 255 0 255; /* Green - excellent */ - } - } - } - } -} diff --git a/e_iwd.spec b/e_iwd.spec new file mode 100644 index 0000000..877888c --- /dev/null +++ b/e_iwd.spec @@ -0,0 +1,40 @@ +Name: e_iwd +Version: 0.1.0 +Release: 1%{?dist} +Summary: Enlightenment Wi-Fi module backed by iwd +License: GPL-2.0-or-later +URL: https://example.invalid/e_iwd +Source0: %{name}-%{version}.tar.xz + +BuildRequires: meson +BuildRequires: gcc +BuildRequires: pkgconfig(eldbus) +BuildRequires: pkgconfig(elementary) +BuildRequires: pkgconfig(enlightenment) + +Requires: enlightenment +Requires: iwd + +%description +Enlightenment shelf module that manages Wi-Fi connections by talking to +the iwd (Intel Wireless Daemon) D-Bus API directly. Replaces the +ConnMan-based econnman gadget. + +%prep +%autosetup + +%build +%meson +%meson_build + +%install +%meson_install + +%files +%license COPYING +%doc README.md +%{_libdir}/enlightenment/modules/iwd/ + +%changelog +* Wed Apr 08 2026 Maintainer - 0.1.0-1 +- Initial scaffolding: D-Bus core, gadcon gadget, popup, agent, config persistence. diff --git a/meson.build b/meson.build index c46bfb9..718947c 100644 --- a/meson.build +++ b/meson.build @@ -1,77 +1,21 @@ -project('e-iwd', 'c', - version: '0.1.0', - default_options: ['c_std=c11', 'warning_level=2'], - meson_version: '>= 0.56.0' -) +project('e_iwd', 'c', + version : '0.1.0', + license : 'MIT', + default_options : ['c_std=gnu99', 'warning_level=2']) -# Dependencies -enlightenment = dependency('enlightenment') -eldbus = dependency('eldbus') -elementary = dependency('elementary') -ecore = dependency('ecore') -evas = dependency('evas') -edje = dependency('edje') -eina = dependency('eina') - -# Get Enlightenment version and module architecture -e_version = enlightenment.version() - -# Detect system ABI (gnu, musl, etc.) cc = meson.get_compiler('c') -if cc.has_header('features.h') - # GNU libc systems - system_abi = 'gnu' -else - # Try to detect from system - fallback to 'unknown' - system_abi = 'unknown' -endif -# Installation paths -module_name = 'iwd' -# Format: --- -# Example: linux-gnu-x86_64-0.27.1 -module_arch = '@0@-@1@-@2@-@3@'.format( - host_machine.system(), - system_abi, - host_machine.cpu_family(), - e_version -) +eldbus = dependency('eldbus') +elementary = dependency('elementary') +enlightenment = dependency('enlightenment') -dir_module = join_paths(get_option('libdir'), 'enlightenment', 'modules', module_name) -dir_module_arch = join_paths(dir_module, module_arch) +module_arch = enlightenment.get_variable(pkgconfig: 'module_arch', + default_value: 'linux-gnu-@0@'.format(host_machine.cpu())) +module_dir = join_paths(get_option('libdir'), 'enlightenment', 'modules', 'iwd') -# Configuration -conf_data = configuration_data() -conf_data.set_quoted('PACKAGE', meson.project_name()) -conf_data.set_quoted('VERSION', meson.project_version()) -conf_data.set_quoted('MODULE_ARCH', module_arch) +add_project_arguments('-DPACKAGE="e_iwd"', + '-DPACKAGE_VERSION="@0@"'.format(meson.project_version()), + language : 'c') -configure_file( - output: 'config.h', - configuration: conf_data -) - -# Add configuration include and feature test macros -add_project_arguments( - '-include', 'config.h', - '-D_GNU_SOURCE', - language: 'c' -) - -# Internationalization -i18n = import('i18n') -if get_option('nls') - subdir('po') -endif - -# Subdirectories subdir('src') subdir('data') - -# Summary -summary({ - 'Module name': module_name, - 'Module architecture': module_arch, - 'Installation path': dir_module_arch, - 'Enlightenment version': enlightenment.version(), -}, section: 'Configuration') diff --git a/meson_options.txt b/meson_options.txt deleted file mode 100644 index a86193c..0000000 --- a/meson_options.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Build options -option('nls', type: 'boolean', value: true, - description: 'Enable internationalization support') diff --git a/metadata/layout.conf b/metadata/layout.conf deleted file mode 100644 index 7a111f5..0000000 --- a/metadata/layout.conf +++ /dev/null @@ -1,2 +0,0 @@ -masters = gentoo -repo-name = x-eiwd diff --git a/packaging/README.md b/packaging/README.md deleted file mode 100644 index b076465..0000000 --- a/packaging/README.md +++ /dev/null @@ -1,361 +0,0 @@ -# eiwd Packaging - -Distribution-specific packaging files for eiwd. - -## Directory Structure - -``` -packaging/ -├── arch/ -│ └── PKGBUILD # Arch Linux package -├── gentoo/ -│ └── eiwd-0.1.0.ebuild # Gentoo ebuild -├── create-release.sh # Release tarball generator -└── README.md # This file -``` - -## Creating a Release Tarball - -```bash -# From project root -./packaging/create-release.sh 0.1.0 - -# This creates: -# - eiwd-0.1.0.tar.gz -# - eiwd-0.1.0.tar.gz.sha256 -# - eiwd-0.1.0.tar.gz.md5 -``` - -The tarball includes: -- Source code (src/, data/, po/) -- Build system (meson.build, meson_options.txt) -- Documentation (README.md, INSTALL.md, etc.) -- License file (if present) - -## Arch Linux Package - -### Building Locally - -```bash -cd packaging/arch/ - -# Download/create source tarball -# Update sha256sums in PKGBUILD - -# Build package -makepkg -si - -# Or just build without installing -makepkg -``` - -### Publishing to AUR - -1. Create AUR account: https://aur.archlinux.org/register -2. Set up SSH key: https://wiki.archlinux.org/title/AUR_submission_guidelines -3. Clone AUR repository: - ```bash - git clone ssh://aur@aur.archlinux.org/eiwd.git - cd eiwd - ``` -4. Copy PKGBUILD and update: - - Set correct `source` URL - - Update `sha256sums` with actual checksum - - Add .SRCINFO: - ```bash - makepkg --printsrcinfo > .SRCINFO - ``` -5. Commit and push: - ```bash - git add PKGBUILD .SRCINFO - git commit -m "Initial import of eiwd 0.1.0" - git push - ``` - -### Testing Installation - -```bash -# Install from local PKGBUILD -cd packaging/arch/ -makepkg -si - -# Verify installation -pacman -Ql eiwd -ls -R /usr/lib/enlightenment/modules/iwd/ - -# Test module -enlightenment_remote -module-load iwd -``` - -## Gentoo Package - -### Adding to Local Overlay - -```bash -# Create overlay if needed -mkdir -p /usr/local/portage/x11-plugins/eiwd - -# Copy ebuild -cp packaging/gentoo/eiwd-0.1.0.ebuild \ - /usr/local/portage/x11-plugins/eiwd/ - -# Generate manifest -cd /usr/local/portage/x11-plugins/eiwd -ebuild eiwd-0.1.0.ebuild manifest - -# Install -emerge -av eiwd -``` - -### Testing Installation - -```bash -# Build and install -emerge eiwd - -# Verify files -equery files eiwd - -# Test module -enlightenment_remote -module-load iwd -``` - -### Submitting to Gentoo Repository - -1. Create bug report: https://bugs.gentoo.org/ -2. Attach ebuild and provide: - - Package description - - Upstream URL - - License verification - - Testing information (architecture, E version) -3. Monitor for maintainer feedback -4. Address any requested changes - -## Debian/Ubuntu Package - -To create a .deb package: - -```bash -# Install packaging tools -sudo apt install devscripts build-essential debhelper - -# Create debian/ directory structure -mkdir -p debian/source - -# Create required files: -# - debian/control (package metadata) -# - debian/rules (build instructions) -# - debian/changelog (version history) -# - debian/copyright (license info) -# - debian/source/format (package format) - -# Build package -debuild -us -uc - -# Install -sudo dpkg -i ../eiwd_0.1.0-1_amd64.deb -``` - -Example `debian/control`: -``` -Source: eiwd -Section: x11 -Priority: optional -Maintainer: Your Name -Build-Depends: debhelper (>= 13), meson, ninja-build, libefl-all-dev, enlightenment-dev -Standards-Version: 4.6.0 -Homepage: https://github.com/yourusername/eiwd - -Package: eiwd -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, enlightenment (>= 0.25), iwd (>= 1.0) -Recommends: polkit -Description: Enlightenment Wi-Fi module using iwd backend - eiwd provides native Wi-Fi management for the Enlightenment desktop - environment using Intel Wireless Daemon (iwd) as the backend. - . - Features fast scanning, secure authentication, and seamless - integration with Enlightenment's module system. -``` - -## Fedora/RPM Package - -```bash -# Install packaging tools -sudo dnf install rpmdevtools rpmbuild - -# Set up RPM build tree -rpmdev-setuptree - -# Create spec file in ~/rpmbuild/SPECS/eiwd.spec -# Copy tarball to ~/rpmbuild/SOURCES/ - -# Build RPM -rpmbuild -ba ~/rpmbuild/SPECS/eiwd.spec - -# Install -sudo rpm -i ~/rpmbuild/RPMS/x86_64/eiwd-0.1.0-1.fc39.x86_64.rpm -``` - -Example spec file excerpt: -```spec -Name: eiwd -Version: 0.1.0 -Release: 1%{?dist} -Summary: Enlightenment Wi-Fi module using iwd backend - -License: BSD -URL: https://github.com/yourusername/eiwd -Source0: %{name}-%{version}.tar.gz - -BuildRequires: meson ninja-build gcc -BuildRequires: enlightenment-devel efl-devel -Requires: enlightenment >= 0.25 -Requires: efl >= 1.26 -Requires: iwd >= 1.0 - -%description -eiwd provides native Wi-Fi management for Enlightenment using iwd. - -%prep -%autosetup - -%build -%meson -%meson_build - -%install -%meson_install - -%files -%license LICENSE -%doc README.md INSTALL.md -%{_libdir}/enlightenment/modules/iwd/ -``` - -## Generic Installation from Tarball - -For distributions without packages: - -```bash -# Extract tarball -tar -xzf eiwd-0.1.0.tar.gz -cd eiwd-0.1.0 - -# Build and install -meson setup build --prefix=/usr/local -ninja -C build -sudo ninja -C build install - -# Module will be installed to: -# /usr/local/lib64/enlightenment/modules/iwd/ -``` - -## Packaging Checklist - -Before releasing a package: - -- [ ] Version number updated in: - - [ ] `meson.build` (project version) - - [ ] PKGBUILD (pkgver) - - [ ] ebuild (filename and PV) - - [ ] debian/changelog - - [ ] spec file - -- [ ] Source tarball created and tested: - - [ ] Extracts cleanly - - [ ] Builds successfully - - [ ] All files included - - [ ] Checksums generated - -- [ ] Documentation up to date: - - [ ] README.md reflects current features - - [ ] INSTALL.md has correct paths - - [ ] CONTRIBUTING.md guidelines current - -- [ ] Package metadata correct: - - [ ] Dependencies accurate - - [ ] License specified - - [ ] Homepage/URL set - - [ ] Description clear - -- [ ] Installation tested: - - [ ] Module loads in Enlightenment - - [ ] Files installed to correct paths - - [ ] No missing dependencies - - [ ] Uninstall works cleanly - -- [ ] Distribution-specific: - - [ ] Arch: .SRCINFO generated - - [ ] Gentoo: Manifest created - - [ ] Debian: Lintian clean - - [ ] Fedora: rpmlint passes - -## Version Numbering - -Follow semantic versioning (semver): - -- **0.1.0** - Initial release -- **0.1.1** - Bug fix release -- **0.2.0** - New features (backward compatible) -- **1.0.0** - First stable release -- **1.1.0** - New features post-1.0 -- **2.0.0** - Breaking changes - -## Distribution Maintainer Notes - -### System Integration - -Packages should: -- Install to standard library paths (`/usr/lib64` or `/usr/lib`) -- Include documentation in `/usr/share/doc/eiwd/` -- Not conflict with other Wi-Fi managers -- Recommend but not require polkit - -### Dependencies - -**Build-time**: -- meson >= 0.56 -- ninja -- gcc/clang -- pkg-config -- edje_cc (part of EFL) - -**Runtime**: -- enlightenment >= 0.25 -- efl >= 1.26 (elementary, eldbus, ecore, evas, edje, eina) -- iwd >= 1.0 -- dbus - -**Optional**: -- polkit (for non-root Wi-Fi management) -- gettext (for translations) - -### Post-Install - -Inform users to: -1. Enable iwd service -2. Configure polkit rules (provide example) -3. Load module in Enlightenment -4. Add gadget to shelf - -### Known Issues - -- Conflicts with wpa_supplicant (both should not run simultaneously) -- Requires D-Bus system bus access -- May need additional polkit configuration on some distributions - -## Support - -For packaging questions: -- Open an issue on GitHub -- Check distribution-specific guidelines -- Refer to INSTALL.md for detailed setup - -## Resources - -- [Arch Linux Packaging Standards](https://wiki.archlinux.org/title/Arch_package_guidelines) -- [Gentoo ebuild Writing Guide](https://devmanual.gentoo.org/ebuild-writing/) -- [Debian Packaging Tutorial](https://www.debian.org/doc/manuals/maint-guide/) -- [Fedora RPM Guide](https://docs.fedoraproject.org/en-US/packaging-guidelines/) -- [iwd Documentation](https://iwd.wiki.kernel.org/) diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD deleted file mode 100644 index 66154ad..0000000 --- a/packaging/arch/PKGBUILD +++ /dev/null @@ -1,51 +0,0 @@ -# Maintainer: Your Name - -pkgname=eiwd -pkgver=0.1.0 -pkgrel=1 -pkgdesc="Enlightenment Wi-Fi module using iwd backend" -arch=('x86_64' 'i686' 'aarch64') -url="https://github.com/yourusername/eiwd" -license=('BSD') # Adjust based on chosen license -depends=('enlightenment>=0.25' 'efl>=1.26' 'iwd>=1.0' 'dbus') -makedepends=('meson' 'ninja' 'gcc') -optdepends=('polkit: for non-root Wi-Fi management') -source=("${pkgname}-${pkgver}.tar.gz") -sha256sums=('SKIP') # Update with actual checksum for release - -build() { - cd "${srcdir}/${pkgname}-${pkgver}" - - meson setup build \ - --prefix=/usr \ - --libdir=lib \ - --buildtype=release \ - -Dnls=true - - ninja -C build -} - -check() { - cd "${srcdir}/${pkgname}-${pkgver}" - - # Run tests if available - # meson test -C build - - # Verify artifacts exist - test -f build/src/module.so - test -f build/data/e-module-iwd.edj -} - -package() { - cd "${srcdir}/${pkgname}-${pkgver}" - - DESTDIR="${pkgdir}" ninja -C build install - - # Install documentation - install -Dm644 README.md "${pkgdir}/usr/share/doc/${pkgname}/README.md" - install -Dm644 INSTALL.md "${pkgdir}/usr/share/doc/${pkgname}/INSTALL.md" - install -Dm644 CONTRIBUTING.md "${pkgdir}/usr/share/doc/${pkgname}/CONTRIBUTING.md" - - # Install license (adjust path/name as needed) - # install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" -} diff --git a/packaging/create-release.sh b/packaging/create-release.sh deleted file mode 100755 index 1154438..0000000 --- a/packaging/create-release.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/bash -# Release tarball creation script for eiwd - -set -e - -VERSION=${1:-"0.1.0"} -PKGNAME="eiwd-${VERSION}" -TARBALL="${PKGNAME}.tar.gz" - -echo "Creating release tarball for eiwd version ${VERSION}" - -# Ensure we're in the project root -cd "$(dirname "$0")/.." - -# Clean any existing build artifacts -echo "Cleaning build artifacts..." -rm -rf build/ -rm -f "${TARBALL}" - -# Create temporary directory for staging -TMPDIR=$(mktemp -d) -STAGEDIR="${TMPDIR}/${PKGNAME}" - -echo "Staging files in ${STAGEDIR}..." - -# Create staging directory -mkdir -p "${STAGEDIR}" - -# Copy source files -cp -r src/ "${STAGEDIR}/" -cp -r data/ "${STAGEDIR}/" -cp -r po/ "${STAGEDIR}/" - -# Copy build files -cp meson.build "${STAGEDIR}/" -cp meson_options.txt "${STAGEDIR}/" - -# Copy documentation -cp README.md INSTALL.md CONTRIBUTING.md TESTING.md "${STAGEDIR}/" - -# Copy license (if exists) -[ -f LICENSE ] && cp LICENSE "${STAGEDIR}/" - -# Copy .gitignore -cp .gitignore "${STAGEDIR}/" - -# Create tarball -echo "Creating tarball ${TARBALL}..." -tar -czf "${TARBALL}" -C "${TMPDIR}" "${PKGNAME}" - -# Generate checksums -echo "Generating checksums..." -sha256sum "${TARBALL}" > "${TARBALL}.sha256" -md5sum "${TARBALL}" > "${TARBALL}.md5" - -# Cleanup -rm -rf "${TMPDIR}" - -# Display results -echo "" -echo "Release tarball created successfully:" -ls -lh "${TARBALL}" -echo "" -echo "SHA256:" -cat "${TARBALL}.sha256" -echo "" -echo "MD5:" -cat "${TARBALL}.md5" -echo "" -echo "To test the tarball:" -echo " tar -xzf ${TARBALL}" -echo " cd ${PKGNAME}" -echo " meson setup build && ninja -C build" diff --git a/packaging/gentoo/eiwd-0.1.0.ebuild b/packaging/gentoo/eiwd-0.1.0.ebuild deleted file mode 100644 index b5433d1..0000000 --- a/packaging/gentoo/eiwd-0.1.0.ebuild +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 1999-2025 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit meson - -DESCRIPTION="Enlightenment Wi-Fi module using iwd backend" -HOMEPAGE="https://git.nemunai.re/nemunaire/eiwd" -SRC_URI="https://git.nemunai.re/nemunaire/eiwd/archive/v${PV}.tar.gz -> ${P}.tar.gz" - -LICENSE="BSD" -SLOT="0" -KEYWORDS="~amd64 ~x86 ~arm64" -IUSE="nls" - -S="${WORKDIR}/${PN}" - -RDEPEND=" - >=x11-wm/enlightenment-0.25.0 - >=dev-libs/efl-1.26.0 - >=net-wireless/iwd-1.0 - sys-apps/dbus -" - -DEPEND="${RDEPEND}" - -BDEPEND=" - >=dev-build/meson-0.56.0 - virtual/pkgconfig - nls? ( sys-devel/gettext ) -" - -DOCS=( README.md INSTALL.md CONTRIBUTING.md ) - -src_configure() { - local emesonargs=( - $(meson_use nls) - ) - meson_src_configure -} - -src_install() { - meson_src_install - einstalldocs -} - -pkg_postinst() { - elog "To use eiwd, you need to:" - elog "1. Ensure iwd service is running: rc-service iwd start" - elog "2. Enable the module in Enlightenment: Settings -> Modules -> IWD" - elog "3. Add the gadget to your shelf" - elog "" - elog "For non-root Wi-Fi management, configure polkit rules." - elog "See /usr/share/doc/${PF}/INSTALL.md for details." -} diff --git a/packaging/profiles/repo_name b/packaging/profiles/repo_name deleted file mode 100644 index 6506ebe..0000000 --- a/packaging/profiles/repo_name +++ /dev/null @@ -1 +0,0 @@ -x-eiwd diff --git a/po/POTFILES.in b/po/POTFILES.in deleted file mode 100644 index b85159b..0000000 --- a/po/POTFILES.in +++ /dev/null @@ -1,10 +0,0 @@ -# List of source files which contain translatable strings -src/e_mod_main.c -src/e_mod_config.c -src/e_mod_gadget.c -src/e_mod_popup.c -src/ui/wifi_auth.c -src/ui/wifi_hidden.c -src/iwd/iwd_dbus.c -src/iwd/iwd_network.c -src/iwd/iwd_device.c diff --git a/po/meson.build b/po/meson.build deleted file mode 100644 index b99f819..0000000 --- a/po/meson.build +++ /dev/null @@ -1,7 +0,0 @@ -# i18n support -i18n.gettext('eiwd', - args: [ - '--directory=' + meson.source_root(), - '--from-code=UTF-8', - ] -) diff --git a/src/e_mod_config.c b/src/e_mod_config.c index 281274c..50bfdd6 100644 --- a/src/e_mod_config.c +++ b/src/e_mod_config.c @@ -1,162 +1,146 @@ #include "e_mod_main.h" +#include "e_mod_config.h" +#include -/* Configuration dialog structure */ -typedef struct _E_Config_Dialog_Data -{ - int auto_connect; - int show_hidden_networks; - int signal_refresh_interval; - char *preferred_adapter; -} E_Config_Dialog_Data; +#define CONFIG_DOMAIN "module.iwd" +#define CONFIG_VERSION 1 -/* Forward declarations */ -static void *_create_data(E_Config_Dialog *cfd); -static void _free_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata); -static Evas_Object *_basic_create(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata); -static int _basic_apply(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata); +E_Iwd_Config *e_iwd_config = NULL; +static E_Config_DD *_edd = NULL; -/* Show configuration dialog */ -void -e_iwd_config_show(void) -{ - E_Config_Dialog *cfd; - E_Config_Dialog_View *v; - - if (!iwd_mod || !iwd_mod->conf) return; - - /* Check if dialog already exists */ - if (e_config_dialog_find("IWD", "extensions/iwd")) - return; - - v = E_NEW(E_Config_Dialog_View, 1); - if (!v) return; - - v->create_cfdata = _create_data; - v->free_cfdata = _free_data; - v->basic.create_widgets = _basic_create; - v->basic.apply_cfdata = _basic_apply; - - cfd = e_config_dialog_new(NULL, "IWD Wi-Fi Configuration", - "IWD", "extensions/iwd", - NULL, 0, v, NULL); - - if (!cfd) - { - E_FREE(v); - return; - } -} - -/* Create config data */ -static void * -_create_data(E_Config_Dialog *cfd EINA_UNUSED) -{ - E_Config_Dialog_Data *cfdata; - - if (!iwd_mod || !iwd_mod->conf) return NULL; - - cfdata = E_NEW(E_Config_Dialog_Data, 1); - if (!cfdata) return NULL; - - /* Copy current config */ - cfdata->auto_connect = iwd_mod->conf->auto_connect; - cfdata->show_hidden_networks = iwd_mod->conf->show_hidden_networks; - cfdata->signal_refresh_interval = iwd_mod->conf->signal_refresh_interval; - - if (iwd_mod->conf->preferred_adapter) - cfdata->preferred_adapter = strdup(iwd_mod->conf->preferred_adapter); - else - cfdata->preferred_adapter = NULL; - - return cfdata; -} - -/* Free config data */ static void -_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata) +_edd_setup(void) { - if (!cfdata) return; - - if (cfdata->preferred_adapter) - free(cfdata->preferred_adapter); - - E_FREE(cfdata); + if (_edd) return; + _edd = E_CONFIG_DD_NEW("E_Iwd_Config", E_Iwd_Config); + E_CONFIG_VAL(_edd, E_Iwd_Config, version, INT); + E_CONFIG_VAL(_edd, E_Iwd_Config, auto_connect, INT); + E_CONFIG_VAL(_edd, E_Iwd_Config, show_hidden, INT); + E_CONFIG_VAL(_edd, E_Iwd_Config, refresh_interval, INT); + E_CONFIG_VAL(_edd, E_Iwd_Config, preferred_adapter, STR); +} + +void +e_iwd_config_load(void) +{ + _edd_setup(); + e_iwd_config = e_config_domain_load(CONFIG_DOMAIN, _edd); + if (e_iwd_config && e_iwd_config->version == CONFIG_VERSION) return; + + /* Missing or out-of-date — start fresh with defaults. */ + if (e_iwd_config) + { + if (e_iwd_config->preferred_adapter) + eina_stringshare_del(e_iwd_config->preferred_adapter); + free(e_iwd_config); + } + e_iwd_config = E_NEW(E_Iwd_Config, 1); + e_iwd_config->version = CONFIG_VERSION; + e_iwd_config->auto_connect = 1; + e_iwd_config->show_hidden = 0; + e_iwd_config->refresh_interval = 5; +} + +void +e_iwd_config_save(void) +{ + if (!_edd || !e_iwd_config) return; + e_config_domain_save(CONFIG_DOMAIN, _edd, e_iwd_config); +} + +/* ----- Settings dialog ------------------------------------------------ */ + +struct _E_Config_Dialog_Data +{ + int auto_connect; + int show_hidden; + int refresh_interval; + char *preferred_adapter; +}; + +static void * +_cfd_create(E_Config_Dialog *cfd EINA_UNUSED) +{ + if (!e_iwd_config) return NULL; + E_Config_Dialog_Data *c = E_NEW(E_Config_Dialog_Data, 1); + c->auto_connect = e_iwd_config->auto_connect; + c->show_hidden = e_iwd_config->show_hidden; + c->refresh_interval = e_iwd_config->refresh_interval; + c->preferred_adapter = e_iwd_config->preferred_adapter + ? strdup(e_iwd_config->preferred_adapter) : strdup(""); + return c; +} + +static void +_cfd_free(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *c) +{ + if (!c) return; + free(c->preferred_adapter); + E_FREE(c); } -/* Create basic UI */ static Evas_Object * -_basic_create(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *cfdata) +_cfd_basic_create(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *c) { Evas_Object *o, *of, *ob; - o = e_widget_list_add(evas, 0, 0); - /* Connection settings frame */ - of = e_widget_framelist_add(evas, "Connection Settings", 0); - + of = e_widget_framelist_add(evas, "Connection", 0); ob = e_widget_check_add(evas, "Auto-connect to known networks", - &(cfdata->auto_connect)); + &c->auto_connect); e_widget_framelist_object_append(of, ob); - ob = e_widget_check_add(evas, "Show hidden networks", - &(cfdata->show_hidden_networks)); + &c->show_hidden); e_widget_framelist_object_append(of, ob); - e_widget_list_object_append(o, of, 1, 1, 0.5); - /* Performance settings frame */ of = e_widget_framelist_add(evas, "Performance", 0); - - ob = e_widget_label_add(evas, "Signal refresh interval (seconds):"); + ob = e_widget_label_add(evas, "Signal refresh interval (s):"); e_widget_framelist_object_append(of, ob); - ob = e_widget_slider_add(evas, 1, 0, "%1.0f", 1.0, 60.0, 1.0, 0, - NULL, &(cfdata->signal_refresh_interval), 150); + NULL, &c->refresh_interval, 150); e_widget_framelist_object_append(of, ob); - e_widget_list_object_append(o, of, 1, 1, 0.5); - /* Adapter settings frame (if multiple adapters available) */ - Eina_List *devices = iwd_devices_get(); - if (eina_list_count(devices) > 1) - { - of = e_widget_framelist_add(evas, "Adapter Selection", 0); - - ob = e_widget_label_add(evas, "Preferred wireless adapter:"); - e_widget_framelist_object_append(of, ob); - - /* TODO: Add radio list for adapter selection when multiple devices exist */ - ob = e_widget_label_add(evas, "(Auto-select)"); - e_widget_framelist_object_append(of, ob); - - e_widget_list_object_append(o, of, 1, 1, 0.5); - } + of = e_widget_framelist_add(evas, "Adapter", 0); + ob = e_widget_label_add(evas, "Preferred wireless adapter (blank = auto):"); + e_widget_framelist_object_append(of, ob); + ob = e_widget_entry_add(evas, &c->preferred_adapter, NULL, NULL, NULL); + e_widget_framelist_object_append(of, ob); + e_widget_list_object_append(o, of, 1, 1, 0.5); return o; } -/* Apply configuration */ static int -_basic_apply(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata) +_cfd_basic_apply(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *c) { - if (!iwd_mod || !iwd_mod->conf) return 0; - - /* Update config */ - iwd_mod->conf->auto_connect = cfdata->auto_connect; - iwd_mod->conf->show_hidden_networks = cfdata->show_hidden_networks; - iwd_mod->conf->signal_refresh_interval = cfdata->signal_refresh_interval; - - if (cfdata->preferred_adapter) - { - if (iwd_mod->conf->preferred_adapter) - eina_stringshare_del(iwd_mod->conf->preferred_adapter); - iwd_mod->conf->preferred_adapter = eina_stringshare_add(cfdata->preferred_adapter); - } - - /* Save config */ - e_config_save_queue(); - - DBG("Configuration updated"); - + if (!e_iwd_config || !c) return 0; + e_iwd_config->auto_connect = c->auto_connect; + e_iwd_config->show_hidden = c->show_hidden; + e_iwd_config->refresh_interval = c->refresh_interval; + if (e_iwd_config->preferred_adapter) + eina_stringshare_del(e_iwd_config->preferred_adapter); + e_iwd_config->preferred_adapter = + (c->preferred_adapter && *c->preferred_adapter) + ? eina_stringshare_add(c->preferred_adapter) : NULL; + e_iwd_config_save(); return 1; } + +void +e_iwd_config_dialog_show(void) +{ + if (e_config_dialog_find("E_Iwd", "extensions/iwd")) return; + + E_Config_Dialog_View *v = E_NEW(E_Config_Dialog_View, 1); + if (!v) return; + v->create_cfdata = _cfd_create; + v->free_cfdata = _cfd_free; + v->basic.create_widgets = _cfd_basic_create; + v->basic.apply_cfdata = _cfd_basic_apply; + + E_Config_Dialog *cfd = e_config_dialog_new(NULL, + "iwd Wi-Fi Settings", "E_Iwd", "extensions/iwd", NULL, 0, v, NULL); + if (!cfd) E_FREE(v); +} diff --git a/src/e_mod_config.h b/src/e_mod_config.h new file mode 100644 index 0000000..bf10573 --- /dev/null +++ b/src/e_mod_config.h @@ -0,0 +1,21 @@ +#ifndef E_MOD_CONFIG_H +#define E_MOD_CONFIG_H + +typedef struct _E_Iwd_Config E_Iwd_Config; + +struct _E_Iwd_Config +{ + int version; + int auto_connect; + int show_hidden; + int refresh_interval; + const char *preferred_adapter; /* eina_stringshare */ +}; + +extern E_Iwd_Config *e_iwd_config; + +void e_iwd_config_load(void); +void e_iwd_config_save(void); +void e_iwd_config_dialog_show(void); + +#endif diff --git a/src/e_mod_gadget.c b/src/e_mod_gadget.c index 78ff615..45d278f 100644 --- a/src/e_mod_gadget.c +++ b/src/e_mod_gadget.c @@ -1,368 +1,298 @@ #include "e_mod_main.h" -#include +#include "e_mod_gadget.h" +#include "e_mod_popup.h" +#include "e_mod_config.h" +#include "iwd/iwd_manager.h" +#include "iwd/iwd_device.h" +#include "iwd/iwd_network.h" +#include -/* Forward declarations */ -static E_Gadcon_Client *_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style); -static void _gc_shutdown(E_Gadcon_Client *gcc); -static void _gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient); -static const char *_gc_label(const E_Gadcon_Client_Class *client_class); -static Evas_Object *_gc_icon(const E_Gadcon_Client_Class *client_class, Evas *evas); -static const char *_gc_id_new(const E_Gadcon_Client_Class *client_class); +/* ----- per-instance gadget data --------------------------------------- */ -static void _gadget_mouse_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info); -static void _gadget_update(Instance *inst); -static Eina_Bool _gadget_update_timer_cb(void *data); - -/* Gadcon class definition */ -static const E_Gadcon_Client_Class _gc_class = +typedef struct _Instance { - GADCON_CLIENT_CLASS_VERSION, - "iwd", - { - _gc_init, _gc_shutdown, _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL, NULL - }, - E_GADCON_CLIENT_STYLE_PLAIN -}; + E_Gadcon_Client *gcc; + Evas_Object *o_base; /* themed edje, gcc->o_base */ + Evas_Object *o_icon; /* swallowed into o_base */ +} Instance; -/* Global gadcon provider */ -static E_Gadcon_Client_Class *gadcon_class = NULL; +static Eina_List *_instances = NULL; -/* Initialize gadget subsystem */ -void -e_iwd_gadget_init(void) +/* ----- icon update ----------------------------------------------------- */ + +/* Walk the manager state to find the network we're currently connected to, + * if any. Used both for the signal-tier icon and for the tooltip. */ +static Iwd_Network * +_active_network(void) { - DBG("Initializing gadget"); - - gadcon_class = (E_Gadcon_Client_Class *)&_gc_class; - e_gadcon_provider_register(gadcon_class); + if (!e_iwd || !e_iwd->manager) return NULL; + const Eina_Hash *devs = iwd_manager_devices(e_iwd->manager); + const Eina_Hash *nets = iwd_manager_networks(e_iwd->manager); + if (!devs || !nets) return NULL; + Eina_Iterator *it = eina_hash_iterator_data_new((Eina_Hash *)devs); + Iwd_Device *d; + Iwd_Network *found = NULL; + EINA_ITERATOR_FOREACH(it, d) + { + if (!d->connected_network) continue; + found = eina_hash_find(nets, d->connected_network); + if (found) break; + } + eina_iterator_free(it); + return found; } -/* Shutdown gadget subsystem */ -void -e_iwd_gadget_shutdown(void) +static const char * +_icon_for_signal_tier(int tier) { - DBG("Shutting down gadget"); - - if (gadcon_class) - { - e_gadcon_provider_unregister(gadcon_class); - gadcon_class = NULL; - } + switch (tier) + { + case 4: return "network-wireless-signal-excellent"; + case 3: return "network-wireless-signal-good"; + case 2: return "network-wireless-signal-ok"; + case 1: return "network-wireless-signal-weak"; + default: return "network-wireless-signal-none"; + } } -/* Gadcon init */ +static const char * +_icon_name_for_state(Iwd_State s) +{ + switch (s) + { + case IWD_STATE_OFF: return "network-offline"; + case IWD_STATE_IDLE: return "network-wireless-disconnected"; + case IWD_STATE_SCANNING: return "network-wireless-acquiring"; + case IWD_STATE_CONNECTING: return "network-wireless-acquiring"; + case IWD_STATE_CONNECTED: + { + Iwd_Network *n = _active_network(); + return _icon_for_signal_tier(n ? iwd_network_signal_tier(n) : 0); + } + case IWD_STATE_ERROR: return "network-error"; + } + return "network-wireless"; +} + +static const char * +_state_label(Iwd_State s) +{ + switch (s) + { + case IWD_STATE_OFF: return "Wi-Fi disabled"; + case IWD_STATE_IDLE: return "Disconnected"; + case IWD_STATE_SCANNING: return "Scanning"; + case IWD_STATE_CONNECTING: return "Connecting"; + case IWD_STATE_CONNECTED: return "Connected"; + case IWD_STATE_ERROR: return "Error"; + } + return ""; +} + +static const char * +_sec_label(int s) +{ + /* Iwd_Security values, kept in sync with iwd_network.h. */ + switch (s) { case 0: return "open"; case 1: return "WPA"; + case 2: return "802.1X"; case 3: return "WEP"; } + return "?"; +} + +static void +_build_tooltip(Instance *inst, Iwd_State s) +{ + char buf[256]; + if (s == IWD_STATE_CONNECTED) + { + Iwd_Network *n = _active_network(); + if (n) + snprintf(buf, sizeof(buf), "Wi-Fi: %s — %s — signal %d/4", + n->ssid ? n->ssid : "?", + _sec_label(n->security), + iwd_network_signal_tier(n)); + else + snprintf(buf, sizeof(buf), "Wi-Fi: connected"); + } + else + snprintf(buf, sizeof(buf), "Wi-Fi: %s", _state_label(s)); + elm_object_tooltip_text_set(inst->o_base, buf); +} + +static void +_inst_refresh(Instance *inst) +{ + if (!inst || !inst->o_icon || !e_iwd) return; + Iwd_State s = iwd_manager_state(e_iwd->manager); + e_icon_fdo_icon_set(inst->o_icon, _icon_name_for_state(s)); + _build_tooltip(inst, s); +} + +/* Listener invoked by iwd_manager whenever state changes. */ +static void +_on_manager_change(void *data EINA_UNUSED, Iwd_Manager *m EINA_UNUSED) +{ + Eina_List *l; + Instance *inst; + EINA_LIST_FOREACH(_instances, l, inst) _inst_refresh(inst); +} + +/* ----- click → popup --------------------------------------------------- */ + +static void +_menu_settings_cb(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED) +{ + e_iwd_config_dialog_show(); +} + +static void +_show_menu(Instance *inst, Evas_Event_Mouse_Down *ev) +{ + E_Zone *zone = e_zone_current_get(); + E_Menu *m = e_menu_new(); + E_Menu_Item *mi = e_menu_item_new(m); + e_menu_item_label_set(mi, "Settings"); + e_util_menu_item_theme_icon_set(mi, "preferences-system"); + e_menu_item_callback_set(mi, _menu_settings_cb, inst); + + int x, y; + e_gadcon_canvas_zone_geometry_get(inst->gcc->gadcon, &x, &y, NULL, NULL); + e_menu_activate_mouse(m, zone, x + ev->output.x, y + ev->output.y, + 1, 1, E_MENU_POP_DIRECTION_AUTO, ev->timestamp); +} + +static void +_on_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info) +{ + Evas_Event_Mouse_Down *ev = event_info; + Instance *inst = data; + if (ev->button == 1) + e_iwd_popup_toggle(inst->gcc); + else if (ev->button == 3) + _show_menu(inst, ev); +} + +/* ----- helpers --------------------------------------------------------- */ + +static char * +_theme_path(void) +{ + static char buf[4096]; + if (!e_iwd || !e_iwd->module) return NULL; + snprintf(buf, sizeof(buf), "%s/e-module-iwd.edj", + e_module_dir_get(e_iwd->module)); + return buf; +} + +/* ----- gadcon class ---------------------------------------------------- */ + static E_Gadcon_Client * _gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style) { - Instance *inst; - E_Gadcon_Client *gcc; - Evas_Object *o; + Instance *inst = E_NEW(Instance, 1); + const char *path = _theme_path(); - DBG("Creating gadget instance"); + /* themed edje is the gadcon o_base — its intrinsic min comes from the + * theme group, just like the backlight module. */ + Evas_Object *base = edje_object_add(gc->evas); + edje_object_file_set(base, path, "e/modules/iwd/main"); + evas_object_show(base); + inst->o_base = base; - inst = E_NEW(Instance, 1); - if (!inst) return NULL; + /* the actual fdo icon goes into the swallow part */ + Evas_Object *icon = e_icon_add(gc->evas); + e_icon_fill_inside_set(icon, EINA_TRUE); + e_icon_fdo_icon_set(icon, "network-wireless"); + e_icon_preload_set(icon, EINA_TRUE); + evas_object_show(icon); + edje_object_part_swallow(base, "e.swallow.content", icon); + inst->o_icon = icon; - /* Create edje object */ - o = edje_object_add(gc->evas); + inst->gcc = e_gadcon_client_new(gc, name, id, style, base); + inst->gcc->data = inst; - /* Load theme */ - char theme_path[PATH_MAX]; - snprintf(theme_path, sizeof(theme_path), "%s/e-module-iwd.edj", - e_module_dir_get(iwd_mod->module)); + evas_object_event_callback_add(base, EVAS_CALLBACK_MOUSE_DOWN, + _on_mouse_down, inst); - if (!edje_object_file_set(o, theme_path, "e/modules/iwd/main")) - { - /* Theme not found, use simple colored rectangle as fallback */ - WRN("Failed to load theme from %s", theme_path); - evas_object_color_set(o, 100, 150, 200, 255); - } - - evas_object_show(o); - - /* Pass the object directly to e_gadcon_client_new */ - gcc = e_gadcon_client_new(gc, name, id, style, o); - if (!gcc) - { - evas_object_del(o); - E_FREE(inst); - return NULL; - } - - gcc->data = inst; - inst->gcc = gcc; - inst->icon = o; - inst->gadget = o; - - /* Add mouse event handler */ - evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, - _gadget_mouse_down_cb, inst); - - /* Get first available device */ - Eina_List *devices = iwd_devices_get(); - if (devices && eina_list_count(devices) > 0) - { - inst->device = eina_list_data_get(devices); - DBG("Using device: %s", inst->device->name ? inst->device->name : inst->device->path); - } - else - { - DBG("No Wi-Fi devices available"); - } - - /* Start update timer */ - inst->update_timer = ecore_timer_add(2.0, _gadget_update_timer_cb, inst); - _gadget_update(inst); - - /* Add to module instances */ - if (iwd_mod) - iwd_mod->instances = eina_list_append(iwd_mod->instances, inst); - - return gcc; + _instances = eina_list_append(_instances, inst); + _inst_refresh(inst); + return inst->gcc; } -/* Gadcon shutdown */ static void _gc_shutdown(E_Gadcon_Client *gcc) { - Instance *inst; - - DBG("Destroying gadget instance"); - - if (!(inst = gcc->data)) return; - - /* Remove from module instances */ - if (iwd_mod) - iwd_mod->instances = eina_list_remove(iwd_mod->instances, inst); - - /* Delete popup if open */ - if (inst->popup) - { - iwd_popup_del(inst); - } - - /* Stop timer */ - if (inst->update_timer) - { - ecore_timer_del(inst->update_timer); - inst->update_timer = NULL; - } - - /* Delete icon */ - if (inst->icon) - evas_object_del(inst->icon); - + Instance *inst = gcc->data; + if (!inst) return; + _instances = eina_list_remove(_instances, inst); + if (inst->o_icon) evas_object_del(inst->o_icon); + if (inst->o_base) evas_object_del(inst->o_base); E_FREE(inst); } -/* Gadcon orient */ static void _gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient EINA_UNUSED) { - Instance *inst; - Evas_Coord mw, mh; - - inst = gcc->data; - if (!inst || !inst->icon) return; - - mw = 0; - mh = 0; - edje_object_size_min_get(inst->icon, &mw, &mh); + Instance *inst = gcc->data; + Evas_Coord mw = 0, mh = 0; + if (!inst || !inst->o_base) return; + edje_object_size_min_get(inst->o_base, &mw, &mh); if ((mw < 1) || (mh < 1)) - edje_object_size_min_calc(inst->icon, &mw, &mh); + edje_object_size_min_calc(inst->o_base, &mw, &mh); if (mw < 4) mw = 4; if (mh < 4) mh = 4; e_gadcon_client_aspect_set(gcc, mw, mh); e_gadcon_client_min_size_set(gcc, mw, mh); } -/* Gadcon label */ static const char * -_gc_label(const E_Gadcon_Client_Class *client_class EINA_UNUSED) -{ - return "IWD Wi-Fi"; -} +_gc_label(const E_Gadcon_Client_Class *cc EINA_UNUSED) { return "iwd"; } -/* Gadcon icon */ static Evas_Object * -_gc_icon(const E_Gadcon_Client_Class *client_class EINA_UNUSED, Evas *evas) +_gc_icon(const E_Gadcon_Client_Class *cc EINA_UNUSED, Evas *evas) { - Evas_Object *o; - char theme_path[PATH_MAX]; - - o = edje_object_add(evas); - - /* Try to load theme */ - if (iwd_mod && iwd_mod->module) - { - snprintf(theme_path, sizeof(theme_path), "%s/e-module-iwd.edj", - e_module_dir_get(iwd_mod->module)); - - if (!edje_object_file_set(o, theme_path, "e/modules/iwd/main")) - { - /* Fallback to simple colored box */ - evas_object_color_set(o, 100, 150, 200, 255); - evas_object_resize(o, 16, 16); - } - } - else - { - /* Fallback if module not initialized yet */ - evas_object_color_set(o, 100, 150, 200, 255); - evas_object_resize(o, 16, 16); - } - - evas_object_show(o); - + const char *path = _theme_path(); + Evas_Object *o = edje_object_add(evas); + if (path) edje_object_file_set(o, path, "icon"); return o; } -/* Generate new ID */ static const char * -_gc_id_new(const E_Gadcon_Client_Class *client_class) +_gc_id_new(const E_Gadcon_Client_Class *cc) { static char buf[128]; - Mod *mod = iwd_mod; - - snprintf(buf, sizeof(buf), "%s.%d", client_class->name, - mod ? eina_list_count(mod->instances) + 1 : 1); + snprintf(buf, sizeof(buf), "%s.%d", cc->name, + eina_list_count(_instances) + 1); return buf; } -/* Mouse down callback */ -static void -_gadget_mouse_down_cb(void *data, Evas *e EINA_UNUSED, - Evas_Object *obj EINA_UNUSED, - void *event_info) +static const E_Gadcon_Client_Class _gadcon_class = { - Instance *inst = data; - Evas_Event_Mouse_Down *ev = event_info; + GADCON_CLIENT_CLASS_VERSION, + "iwd", + { _gc_init, _gc_shutdown, _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL, NULL }, + E_GADCON_CLIENT_STYLE_PLAIN +}; - if (!inst) - { - e_util_dialog_show("Debug", "Instance is NULL!"); - return; - } +/* ----- public ---------------------------------------------------------- */ - if (ev->button == 1) /* Left click */ - { - INF("Gadget clicked - popup=%p device=%p", inst->popup, inst->device); - - if (inst->popup) - { - /* Close popup */ - INF("Closing popup"); - iwd_popup_del(inst); - } - else - { - /* Open popup */ - INF("Opening popup"); - iwd_popup_new(inst); - - /* Debug: Check if popup was created */ - if (!inst->popup) - { - ERR("Failed to create popup!"); - e_util_dialog_show("IWD Debug", - "Popup creation failed.
" - "Check if iwd is running and wireless device exists."); - } - } - } +void +e_iwd_gadget_init(void) +{ + e_gadcon_provider_register(&_gadcon_class); + if (e_iwd && e_iwd->manager) + iwd_manager_listener_add(e_iwd->manager, _on_manager_change, NULL); } -/* Update gadget icon and tooltip */ -static void -_gadget_update(Instance *inst) +void +e_iwd_gadget_shutdown(void) { - char buf[256]; - - if (!inst) return; - - /* Update tooltip */ - if (inst->device) - { - if (inst->device->state && strcmp(inst->device->state, "connected") == 0) - { - snprintf(buf, sizeof(buf), "IWD Wi-Fi\nConnected: %s\nSignal: %s", - inst->device->name ? inst->device->name : "Unknown", - inst->device->connected_network ? "Good" : ""); - } - else if (inst->device->state && strcmp(inst->device->state, "connecting") == 0) - { - snprintf(buf, sizeof(buf), "IWD Wi-Fi\nConnecting..."); - } - else - { - snprintf(buf, sizeof(buf), "IWD Wi-Fi\nDisconnected"); - } - } - else - { - snprintf(buf, sizeof(buf), "IWD Wi-Fi\nNo device"); - } - - /* Update icon appearance using Edje signals */ - extern IWD_State iwd_state_get(void); - IWD_State state = iwd_state_get(); - const char *file = NULL; - - /* Check if theme is loaded */ - if (inst->icon) - { - edje_object_file_get(inst->icon, &file, NULL); - } - - if (inst->icon && file) - { - /* Icon has theme loaded, use signals */ - switch (state) - { - case IWD_STATE_CONNECTED: - edje_object_signal_emit(inst->icon, "e,state,connected", "e"); - edje_object_signal_emit(inst->icon, "e,signal,show", "e"); - break; - case IWD_STATE_CONNECTING: - edje_object_signal_emit(inst->icon, "e,state,connecting", "e"); - edje_object_signal_emit(inst->icon, "e,signal,hide", "e"); - break; - case IWD_STATE_ERROR: - edje_object_signal_emit(inst->icon, "e,state,error", "e"); - edje_object_signal_emit(inst->icon, "e,signal,hide", "e"); - break; - default: - edje_object_signal_emit(inst->icon, "e,state,disconnected", "e"); - edje_object_signal_emit(inst->icon, "e,signal,hide", "e"); - break; - } - } - else - { - /* Fallback to color changes if no theme */ - if (inst->device && inst->device->state) - { - if (strcmp(inst->device->state, "connected") == 0) - evas_object_color_set(inst->icon, 100, 200, 100, 255); /* Green */ - else if (strcmp(inst->device->state, "connecting") == 0) - evas_object_color_set(inst->icon, 200, 200, 100, 255); /* Yellow */ - else - evas_object_color_set(inst->icon, 150, 150, 150, 255); /* Gray */ - } - else - { - evas_object_color_set(inst->icon, 200, 100, 100, 255); /* Red - no device */ - } - } + if (e_iwd && e_iwd->manager) + iwd_manager_listener_del(e_iwd->manager, _on_manager_change, NULL); + e_gadcon_provider_unregister(&_gadcon_class); } -/* Update timer callback */ -static Eina_Bool -_gadget_update_timer_cb(void *data) +void +e_iwd_gadget_update(void) { - Instance *inst = data; - - _gadget_update(inst); - - return ECORE_CALLBACK_RENEW; + _on_manager_change(NULL, NULL); } diff --git a/src/e_mod_gadget.h b/src/e_mod_gadget.h new file mode 100644 index 0000000..4b7dcf6 --- /dev/null +++ b/src/e_mod_gadget.h @@ -0,0 +1,8 @@ +#ifndef E_MOD_GADGET_H +#define E_MOD_GADGET_H + +void e_iwd_gadget_init(void); +void e_iwd_gadget_shutdown(void); +void e_iwd_gadget_update(void); + +#endif diff --git a/src/e_mod_main.c b/src/e_mod_main.c index cf4bbff..160316a 100644 --- a/src/e_mod_main.c +++ b/src/e_mod_main.c @@ -1,227 +1,58 @@ #include "e_mod_main.h" +#include "iwd/iwd_manager.h" +#include "e_mod_gadget.h" +#include "e_mod_popup.h" +#include "e_mod_config.h" -/* Module metadata */ -E_API E_Module_Api e_modapi = { - E_MODULE_API_VERSION, - "IWD" -}; +E_Iwd_Module *e_iwd = NULL; -/* Global module instance */ -Mod *iwd_mod = NULL; +EAPI E_Module_Api e_modapi = { E_MODULE_API_VERSION, "iwd" }; -/* Logging domain */ -int _e_iwd_log_dom = -1; - -/* Forward declarations */ -static void _iwd_config_load(void); -static void _iwd_config_free(void); - -/* Module initialization */ -E_API void * +EAPI void * e_modapi_init(E_Module *m) { - Mod *mod; + e_iwd = E_NEW(E_Iwd_Module, 1); + e_iwd->module = m; - /* Initialize logging */ - _e_iwd_log_dom = eina_log_domain_register("e-iwd", EINA_COLOR_CYAN); - if (_e_iwd_log_dom < 0) - { - EINA_LOG_ERR("Could not register log domain 'e-iwd'"); - return NULL; - } + if (!eldbus_init()) + { + E_FREE(e_iwd); + return NULL; + } - INF("IWD Module initializing"); + e_iwd->conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM); + if (!e_iwd->conn) + { + eldbus_shutdown(); + E_FREE(e_iwd); + return NULL; + } - /* Allocate module structure */ - mod = E_NEW(Mod, 1); - if (!mod) - { - ERR("Failed to allocate module structure"); - eina_log_domain_unregister(_e_iwd_log_dom); - _e_iwd_log_dom = -1; - return NULL; - } - - mod->module = m; - mod->log_dom = _e_iwd_log_dom; - iwd_mod = mod; - - /* Initialize configuration */ - e_iwd_config_init(); - _iwd_config_load(); - - /* Initialize D-Bus and iwd subsystems (Phase 2 & 5) */ - iwd_state_init(); - iwd_device_init(); - iwd_network_init(); - - if (!iwd_dbus_init()) - { - WRN("Failed to initialize D-Bus connection to iwd"); - iwd_state_set(IWD_STATE_ERROR); - /* Continue anyway - we'll show error state in UI */ - } - - if (!iwd_agent_init()) - { - WRN("Failed to initialize iwd agent"); - } - - /* Initialize gadget (Phase 3) */ + e_iwd_config_load(); + e_iwd->manager = iwd_manager_new(e_iwd->conn); + e_iwd_popup_install_passphrase_handler(); e_iwd_gadget_init(); - INF("IWD Module initialized successfully"); - return mod; + return m; } -/* Module shutdown */ -E_API int +EAPI int e_modapi_shutdown(E_Module *m EINA_UNUSED) { - Mod *mod = iwd_mod; + if (!e_iwd) return 1; - if (!mod) return 0; - - INF("IWD Module shutting down"); - - /* Shutdown gadget */ e_iwd_gadget_shutdown(); - - /* Shutdown iwd subsystems (must happen before D-Bus shutdown) */ - iwd_network_shutdown(); - iwd_device_shutdown(); - iwd_state_shutdown(); - - /* Shutdown D-Bus (this frees all proxies and handlers) */ - iwd_agent_shutdown(); - iwd_dbus_shutdown(); - - /* Free configuration */ - _iwd_config_free(); - e_iwd_config_shutdown(); - - /* Free module structure */ - E_FREE(mod); - iwd_mod = NULL; - - /* Unregister logging */ - eina_log_domain_unregister(_e_iwd_log_dom); - _e_iwd_log_dom = -1; - - INF("IWD Module shutdown complete"); + if (e_iwd->manager) iwd_manager_free(e_iwd->manager); + e_iwd_config_save(); + if (e_iwd->conn) eldbus_connection_unref(e_iwd->conn); + eldbus_shutdown(); + E_FREE(e_iwd); return 1; } -/* Module save */ -E_API int +EAPI int e_modapi_save(E_Module *m EINA_UNUSED) { - Mod *mod = iwd_mod; - - if (!mod || !mod->conf) return 0; - - DBG("Saving module configuration"); - return e_config_domain_save("module.iwd", mod->conf_edd, mod->conf); + e_iwd_config_save(); + return 1; } - -/* Configuration management */ -void -e_iwd_config_init(void) -{ - Mod *mod = iwd_mod; - - if (!mod) return; - - /* Create configuration descriptor */ - mod->conf_edd = E_CONFIG_DD_NEW("IWD_Config", Config); - if (!mod->conf_edd) - { - ERR("Failed to create config EDD"); - return; - } - - #undef T - #undef D - #define T Config - #define D mod->conf_edd - - E_CONFIG_VAL(D, T, config_version, INT); - E_CONFIG_VAL(D, T, auto_connect, UCHAR); - E_CONFIG_VAL(D, T, show_hidden_networks, UCHAR); - E_CONFIG_VAL(D, T, signal_refresh_interval, INT); - E_CONFIG_VAL(D, T, preferred_adapter, STR); - - #undef T - #undef D -} - -void -e_iwd_config_shutdown(void) -{ - Mod *mod = iwd_mod; - - if (!mod) return; - - if (mod->conf_edd) - { - E_CONFIG_DD_FREE(mod->conf_edd); - mod->conf_edd = NULL; - } -} - -static void -_iwd_config_load(void) -{ - Mod *mod = iwd_mod; - - if (!mod || !mod->conf_edd) return; - - /* Load configuration from disk */ - mod->conf = e_config_domain_load("module.iwd", mod->conf_edd); - - if (mod->conf) - { - /* Check version */ - if ((mod->conf->config_version >> 16) < MOD_CONFIG_FILE_EPOCH) - { - /* Config too old, use defaults */ - WRN("Configuration version too old, using defaults"); - E_FREE(mod->conf); - mod->conf = NULL; - } - } - - /* Create default configuration if needed */ - if (!mod->conf) - { - INF("Creating default configuration"); - mod->conf = E_NEW(Config, 1); - if (mod->conf) - { - mod->conf->config_version = MOD_CONFIG_FILE_VERSION; - mod->conf->auto_connect = EINA_TRUE; - mod->conf->show_hidden_networks = EINA_FALSE; - mod->conf->signal_refresh_interval = 5; - mod->conf->preferred_adapter = NULL; - - /* Save default config */ - e_config_domain_save("module.iwd", mod->conf_edd, mod->conf); - } - } -} - -static void -_iwd_config_free(void) -{ - Mod *mod = iwd_mod; - - if (!mod || !mod->conf) return; - - if (mod->conf->preferred_adapter) - eina_stringshare_del(mod->conf->preferred_adapter); - - E_FREE(mod->conf); - mod->conf = NULL; -} - -/* Gadget implementations are in e_mod_gadget.c */ diff --git a/src/e_mod_main.h b/src/e_mod_main.h index 8e180c0..1cb3c7a 100644 --- a/src/e_mod_main.h +++ b/src/e_mod_main.h @@ -2,97 +2,26 @@ #define E_MOD_MAIN_H #include -#include #include +#include -/* Module version information */ -#define MOD_CONFIG_FILE_EPOCH 0x0001 -#define MOD_CONFIG_FILE_GENERATION 0x0001 -#define MOD_CONFIG_FILE_VERSION \ - ((MOD_CONFIG_FILE_EPOCH << 16) | MOD_CONFIG_FILE_GENERATION) +typedef struct _E_Iwd_Module E_Iwd_Module; -/* Forward declarations for iwd types */ -typedef struct _IWD_Device IWD_Device; - -/* Configuration structure */ -typedef struct _Config +struct _E_Iwd_Module { - int config_version; - Eina_Bool auto_connect; - Eina_Bool show_hidden_networks; - int signal_refresh_interval; - const char *preferred_adapter; -} Config; + E_Module *module; + Eldbus_Connection *conn; + void *manager; /* Iwd_Manager * */ + void *gadget; /* gadget instance */ + void *config; /* E_Config_Dialog data */ +}; -/* Module instance structure (gadget) */ -typedef struct _Instance -{ - E_Gadcon_Client *gcc; - Evas_Object *gadget; - Evas_Object *icon; - void *popup; /* E_Gadcon_Popup - void to avoid circular dependency */ +extern E_Iwd_Module *e_iwd; - IWD_Device *device; - Ecore_Timer *update_timer; -} Instance; +EAPI extern E_Module_Api e_modapi; -/* Global module context */ -typedef struct _Mod -{ - E_Module *module; - E_Config_DD *conf_edd; - Config *conf; - Eina_List *instances; - - /* D-Bus connection (will be initialized in Phase 2) */ - Eldbus_Connection *dbus_conn; - - /* Logging domain */ - int log_dom; -} Mod; - -/* Global module instance */ -extern Mod *iwd_mod; - -/* Logging macros */ -extern int _e_iwd_log_dom; -#undef DBG -#undef INF -#undef WRN -#undef ERR -#define DBG(...) EINA_LOG_DOM_DBG(_e_iwd_log_dom, __VA_ARGS__) -#define INF(...) EINA_LOG_DOM_INFO(_e_iwd_log_dom, __VA_ARGS__) -#define WRN(...) EINA_LOG_DOM_WARN(_e_iwd_log_dom, __VA_ARGS__) -#define ERR(...) EINA_LOG_DOM_ERR(_e_iwd_log_dom, __VA_ARGS__) - -/* Module API functions */ -E_API extern E_Module_Api e_modapi; -E_API void *e_modapi_init(E_Module *m); -E_API int e_modapi_shutdown(E_Module *m); -E_API int e_modapi_save(E_Module *m); - -/* Configuration functions */ -void e_iwd_config_init(void); -void e_iwd_config_shutdown(void); -void e_iwd_config_show(void); - -/* Gadget functions */ -void e_iwd_gadget_init(void); -void e_iwd_gadget_shutdown(void); - -/* Popup functions */ -void iwd_popup_new(Instance *inst); -void iwd_popup_del(Instance *inst); - -/* UI dialog functions */ -#include "ui/wifi_auth.h" -#include "ui/wifi_hidden.h" - -/* D-Bus functions */ -#include "iwd/iwd_dbus.h" -#include "iwd/iwd_device.h" -#include "iwd/iwd_network.h" -#include "iwd/iwd_agent.h" -#include "iwd/iwd_state.h" +EAPI void *e_modapi_init (E_Module *m); +EAPI int e_modapi_shutdown (E_Module *m); +EAPI int e_modapi_save (E_Module *m); #endif diff --git a/src/e_mod_popup.c b/src/e_mod_popup.c index bbada58..c987cf2 100644 --- a/src/e_mod_popup.c +++ b/src/e_mod_popup.c @@ -1,316 +1,445 @@ #include "e_mod_main.h" +#include "e_mod_popup.h" +#include "iwd/iwd_manager.h" +#include "iwd/iwd_device.h" +#include "iwd/iwd_network.h" +#include "iwd/iwd_agent.h" +#include "ui/wifi_auth.h" +#include "ui/wifi_hidden.h" +#include +#include +#include -/* Forward declarations */ -static void _popup_comp_del_cb(void *data, Evas_Object *obj); -static void _button_rescan_cb(void *data, Evas_Object *obj, void *event_info); -static void _button_disconnect_cb(void *data, Evas_Object *obj, void *event_info); -static void _button_hidden_cb(void *data, Evas_Object *obj, void *event_info); -static Eina_Bool _popup_reopen_cb(void *data); -static void _network_selected_cb(void *data, Evas_Object *obj, void *event_info); - -/* Create popup */ -void -iwd_popup_new(Instance *inst) +typedef struct _Popup { - Evas_Object *list, *box, *button, *label; - E_Gadcon_Popup *popup; - Evas_Coord w, h; - IWD_Network *net; + E_Gadcon_Popup *gp; + Evas_Object *box; + Evas_Object *status_lbl; + Evas_Object *list; + Evas_Object *btn_scan; + Evas_Object *btn_toggle; + Evas_Object *btn_hidden; + Evas_Object *btn_disconnect; /* shown only when connected */ + Evas_Object *action_row; + Eina_Bool listening; +} Popup; + +static Popup *_popup = NULL; + +/* Pending passphrase request from the agent — only one at a time. */ +static Iwd_Agent_Request *_pending_req = NULL; +/* Tracked so iwd's Cancel(reason) can tear down the dialog. */ +static Evas_Object *_pending_dialog = NULL; +/* One-shot passphrase pre-armed by the hidden-network dialog. */ +static char *_hidden_pending_pass = NULL; + +/* ----- helpers --------------------------------------------------------- */ + +static const char * +_state_label(Iwd_State s) +{ + switch (s) { + case IWD_STATE_OFF: return "Wi-Fi disabled"; + case IWD_STATE_IDLE: return "Disconnected"; + case IWD_STATE_SCANNING: return "Scanning…"; + case IWD_STATE_CONNECTING: return "Connecting…"; + case IWD_STATE_CONNECTED: return "Connected"; + case IWD_STATE_ERROR: return "Error"; + } + return ""; +} + +static const char * +_sec_label(Iwd_Security s) +{ + switch (s) { + case IWD_SEC_OPEN: return "open"; + case IWD_SEC_PSK: return "WPA"; + case IWD_SEC_8021X: return "802.1X"; + case IWD_SEC_WEP: return "WEP"; + case IWD_SEC_UNKNOWN: return "?"; + } + return ""; +} + +static int +_net_cmp(const void *a, const void *b) +{ + const Iwd_Network *na = a, *nb = b; + if (na->connected != nb->connected) return nb->connected - na->connected; + if (na->known_path && !nb->known_path) return -1; + if (!na->known_path && nb->known_path) return 1; + /* Higher signal first within same class. */ + int ta = iwd_network_signal_tier(na); + int tb = iwd_network_signal_tier(nb); + if (ta != tb) return tb - ta; + if (!na->ssid) return 1; + if (!nb->ssid) return -1; + return strcasecmp(na->ssid, nb->ssid); +} + +static const char * +_signal_bars(int tier) +{ + switch (tier) + { + case 4: return "▂▄▆█"; + case 3: return "▂▄▆ "; + case 2: return "▂▄ "; + case 1: return "▂ "; + default: return " "; + } +} + +/* First device that has a station; used for "Disconnect" and hidden connect. */ +static Iwd_Device * +_active_device(void) +{ + if (!e_iwd || !e_iwd->manager) return NULL; + const Eina_Hash *h = iwd_manager_devices(e_iwd->manager); + if (!h) return NULL; + Eina_Iterator *it = eina_hash_iterator_data_new((Eina_Hash *)h); + Iwd_Device *d, *best = NULL; + EINA_ITERATOR_FOREACH(it, d) + { + if (!d->has_station) continue; + if (!best) best = d; + if (d->connected_network) { best = d; break; } + } + eina_iterator_free(it); + return best; +} + +/* ----- list rendering -------------------------------------------------- */ + +static void +_on_net_clicked(void *data, Evas_Object *obj EINA_UNUSED, void *ev EINA_UNUSED) +{ + Iwd_Network *n = data; + if (!n) return; + iwd_network_connect(n); +} + +static void +_on_net_forget(void *data, Evas_Object *obj EINA_UNUSED, void *ev EINA_UNUSED) +{ + Iwd_Network *n = data; + if (!n) return; + iwd_network_forget(n); +} + +static void +_rebuild_list(Popup *p) +{ + if (!p->list || !e_iwd || !e_iwd->manager) return; + elm_box_clear(p->list); + + /* When the radio is off, hide the (now-stale) network list entirely. */ + if (iwd_manager_state(e_iwd->manager) == IWD_STATE_OFF) return; + + const Eina_Hash *h = iwd_manager_networks(e_iwd->manager); + if (!h) return; + + /* Snapshot into a list so we can sort. */ + Eina_List *items = NULL; + Eina_Iterator *it = eina_hash_iterator_data_new((Eina_Hash *)h); + Iwd_Network *n; + EINA_ITERATOR_FOREACH(it, n) items = eina_list_append(items, n); + eina_iterator_free(it); + items = eina_list_sort(items, eina_list_count(items), _net_cmp); + Eina_List *l; + EINA_LIST_FOREACH(items, l, n) + { + Evas_Object *row = elm_box_add(p->list); + elm_box_horizontal_set(row, EINA_TRUE); + elm_box_padding_set(row, 4, 0); + evas_object_size_hint_weight_set(row, EVAS_HINT_EXPAND, 0); + evas_object_size_hint_align_set(row, EVAS_HINT_FILL, 0); - if (!inst) - { - ERR("iwd_popup_new: inst is NULL"); - return; - } + Evas_Object *btn = elm_button_add(row); + /* Truncate long SSIDs so the row never forces horizontal scrolling. */ + const char *raw_ssid = n->ssid ? n->ssid : "(hidden)"; + char ssid_buf[32]; + const int max_ssid = 22; + if ((int)strlen(raw_ssid) > max_ssid) + { + snprintf(ssid_buf, sizeof(ssid_buf), "%.*s…", max_ssid - 1, raw_ssid); + raw_ssid = ssid_buf; + } + char label[256]; + snprintf(label, sizeof(label), "%s %s%s [%s]%s", + _signal_bars(iwd_network_signal_tier(n)), + n->known_path ? "★ " : " ", + raw_ssid, + _sec_label(n->security), + n->connected ? " ✔" : ""); + elm_object_text_set(btn, label); + evas_object_size_hint_weight_set(btn, EVAS_HINT_EXPAND, 0); + evas_object_size_hint_align_set(btn, EVAS_HINT_FILL, 0); + evas_object_smart_callback_add(btn, "clicked", _on_net_clicked, n); + elm_box_pack_end(row, btn); + evas_object_show(btn); - if (inst->popup) - { - DBG("Popup already exists"); - return; - } + if (n->known_path) + { + Evas_Object *fb = elm_button_add(row); + elm_object_text_set(fb, "✕"); + elm_object_tooltip_text_set(fb, "Forget network"); + evas_object_smart_callback_add(fb, "clicked", _on_net_forget, n); + elm_box_pack_end(row, fb); + evas_object_show(fb); + } - INF("Creating popup for instance %p", inst); - - /* Create popup */ - popup = e_gadcon_popup_new(inst->gcc, 0); - if (!popup) - { - ERR("e_gadcon_popup_new failed!"); - e_util_dialog_show("IWD Error", "Failed to create gadcon popup"); - return; - } - - inst->popup = (void *)popup; - INF("Popup created: %p", popup); - - /* Create main box */ - box = elm_box_add(e_comp->elm); - elm_box_horizontal_set(box, EINA_FALSE); - elm_box_padding_set(box, 0, 5); - evas_object_show(box); - - /* Title */ - label = elm_label_add(box); - elm_object_text_set(label, "IWD Wi-Fi Manager"); - elm_box_pack_end(box, label); - evas_object_show(label); - - /* Current connection status */ - if (inst->device) - { - Evas_Object *frame = elm_frame_add(box); - elm_object_text_set(frame, "Current Connection"); - - Evas_Object *status_box = elm_box_add(frame); - - char buf[256]; - if (inst->device->state && strcmp(inst->device->state, "connected") == 0) - { - snprintf(buf, sizeof(buf), "Connected to: %s", - inst->device->name ? inst->device->name : "Unknown"); - } - else if (inst->device->state && strcmp(inst->device->state, "connecting") == 0) - { - snprintf(buf, sizeof(buf), "Connecting..."); - } - else - { - snprintf(buf, sizeof(buf), "Disconnected"); - } - - Evas_Object *status_label = elm_label_add(status_box); - elm_object_text_set(status_label, buf); - elm_box_pack_end(status_box, status_label); - evas_object_show(status_label); - - /* Disconnect button if connected */ - if (inst->device->state && strcmp(inst->device->state, "connected") == 0) - { - button = elm_button_add(status_box); - elm_object_text_set(button, "Disconnect"); - evas_object_smart_callback_add(button, "clicked", _button_disconnect_cb, inst); - elm_box_pack_end(status_box, button); - evas_object_show(button); - } - - elm_object_content_set(frame, status_box); - evas_object_show(status_box); - elm_box_pack_end(box, frame); - evas_object_show(frame); - } - - /* Available networks */ - Evas_Object *frame = elm_frame_add(box); - elm_object_text_set(frame, "Available Networks"); - - list = elm_list_add(frame); - elm_list_mode_set(list, ELM_LIST_COMPRESS); - - /* Add networks to list */ - Eina_List *networks = iwd_networks_get(); - int count = 0; - - EINA_LIST_FOREACH(networks, l, net) - { - if (net->name) - { - char item_text[256]; - const char *security = "Open"; - - if (net->type) - { - if (strcmp(net->type, "psk") == 0) - security = "WPA2"; - else if (strcmp(net->type, "8021x") == 0) - security = "Enterprise"; - } - - snprintf(item_text, sizeof(item_text), "%s (%s)%s", - net->name, security, - net->known ? " *" : ""); - - elm_list_item_append(list, item_text, NULL, NULL, _network_selected_cb, net); - count++; - } - } - - if (count == 0) - { - elm_list_item_append(list, "No networks found", NULL, NULL, NULL, NULL); - } - - /* Set select mode to always */ - elm_list_select_mode_set(list, ELM_OBJECT_SELECT_MODE_ALWAYS); - elm_list_go(list); - elm_object_content_set(frame, list); - evas_object_show(list); - elm_box_pack_end(box, frame); - evas_object_show(frame); - - /* Action buttons */ - Evas_Object *button_box = elm_box_add(box); - elm_box_horizontal_set(button_box, EINA_TRUE); - elm_box_padding_set(button_box, 5, 0); - - /* Rescan button */ - button = elm_button_add(button_box); - elm_object_text_set(button, "Rescan"); - evas_object_smart_callback_add(button, "clicked", _button_rescan_cb, inst); - elm_box_pack_end(button_box, button); - evas_object_show(button); - - /* Hidden network button */ - button = elm_button_add(button_box); - elm_object_text_set(button, "Hidden..."); - evas_object_smart_callback_add(button, "clicked", _button_hidden_cb, inst); - elm_box_pack_end(button_box, button); - evas_object_show(button); - - elm_box_pack_end(box, button_box); - evas_object_show(button_box); - - /* Set popup content */ - e_gadcon_popup_content_set(popup, box); - - /* Size the popup */ - evas_object_geometry_get(box, NULL, NULL, &w, &h); - if (w < 200) w = 200; - if (h < 150) h = 150; - if (w > 400) w = 400; - if (h > 500) h = 500; - - evas_object_resize(box, w, h); - - /* Show popup */ - e_gadcon_popup_show(popup); - - /* Auto-close on escape */ - e_comp_object_util_autoclose(popup->comp_object, - _popup_comp_del_cb, - e_comp_object_util_autoclose_on_escape, - inst); - - DBG("Popup created"); + elm_box_pack_end(p->list, row); + evas_object_show(row); + } + eina_list_free(items); } -/* Delete popup */ +static void +_refresh(Popup *p) +{ + if (!p || !e_iwd || !e_iwd->manager) return; + Iwd_State s = iwd_manager_state(e_iwd->manager); + if (p->status_lbl) + elm_object_text_set(p->status_lbl, _state_label(s)); + if (p->btn_toggle) + elm_object_text_set(p->btn_toggle, s == IWD_STATE_OFF ? "Enable" : "Disable"); + if (p->btn_scan) + elm_object_disabled_set(p->btn_scan, s == IWD_STATE_OFF); + if (p->btn_hidden) + elm_object_disabled_set(p->btn_hidden, s == IWD_STATE_OFF); + if (p->btn_disconnect) + { + Eina_Bool show = (s == IWD_STATE_CONNECTED); + if (show) evas_object_show(p->btn_disconnect); + else evas_object_hide(p->btn_disconnect); + } + _rebuild_list(p); +} + +static void +_on_manager_change(void *data, Iwd_Manager *m EINA_UNUSED) +{ + _refresh(data); +} + +/* ----- action buttons -------------------------------------------------- */ + +static void _on_rescan (void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED) +{ + if (e_iwd && e_iwd->manager) iwd_manager_scan_request(e_iwd->manager); +} +static void _on_toggle(void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED) +{ + if (!e_iwd || !e_iwd->manager) return; + Eina_Bool off = (iwd_manager_state(e_iwd->manager) == IWD_STATE_OFF); + iwd_manager_set_powered(e_iwd->manager, off); +} +static void _on_disconnect(void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED) +{ + Iwd_Device *dev = _active_device(); + if (dev) iwd_device_disconnect(dev); +} + +static void +_on_hidden_done(void *data EINA_UNUSED, const char *ssid, const char *pass, Eina_Bool ok) +{ + if (!ok || !ssid || !*ssid) return; + Iwd_Device *dev = _active_device(); + if (!dev) return; + /* Pre-arm the agent reply so the next RequestPassphrase from iwd is + * answered automatically. If the network turns out to be open, the + * stashed passphrase is simply never consumed. */ + if (pass && *pass) _hidden_pending_pass = strdup(pass); + iwd_device_connect_hidden(dev, ssid); +} + +static void _on_hidden(void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED) +{ + wifi_hidden_prompt(_popup ? _popup->box : e_comp->elm, _on_hidden_done, NULL); +} + +/* ----- passphrase plumbing -------------------------------------------- */ + +static void +_on_auth_done(void *data EINA_UNUSED, const char *pass, Eina_Bool ok) +{ + _pending_dialog = NULL; + if (!_pending_req) return; + if (ok) iwd_agent_reply (_pending_req, pass ? pass : ""); + else iwd_agent_cancel(_pending_req); + _pending_req = NULL; +} + +static void +_on_agent_cancel(void *data EINA_UNUSED, const char *reason EINA_UNUSED) +{ + /* iwd dropped the auth attempt — close any open dialog. The dialog's + * DEL handler will fire _on_auth_done(ok=FALSE), but _pending_req has + * already been consumed by iwd, so clear it first to avoid double-cancel. */ + _pending_req = NULL; + if (_pending_dialog) + { + Evas_Object *d = _pending_dialog; + _pending_dialog = NULL; + evas_object_del(d); + } +} + +static void _on_passphrase_request(void *data, Iwd_Agent_Request *req, const char *netpath); + void -iwd_popup_del(Instance *inst) +e_iwd_popup_install_passphrase_handler(void) { - E_Gadcon_Popup *popup; - - if (!inst) return; - if (!inst->popup) return; - - DBG("Deleting popup"); - - popup = (E_Gadcon_Popup *)inst->popup; - e_object_del(E_OBJECT(popup)); - inst->popup = NULL; + if (e_iwd && e_iwd->manager) + { + iwd_manager_set_passphrase_handler(e_iwd->manager, _on_passphrase_request, NULL); + iwd_manager_set_cancel_handler (e_iwd->manager, _on_agent_cancel, NULL); + } } -/* Comp delete callback */ static void -_popup_comp_del_cb(void *data, Evas_Object *obj EINA_UNUSED) +_on_passphrase_request(void *data EINA_UNUSED, Iwd_Agent_Request *req, const char *netpath) { - Instance *inst = data; + /* If the user just kicked off a hidden-network connect with a passphrase, + * answer this request automatically without prompting. */ + if (_hidden_pending_pass) + { + iwd_agent_reply(req, _hidden_pending_pass); + free(_hidden_pending_pass); + _hidden_pending_pass = NULL; + return; + } + if (_pending_req) + { + iwd_agent_cancel(req); + return; + } + _pending_req = req; - if (inst && inst->popup) - iwd_popup_del(inst); + /* Look up the network for a friendly SSID, if we have it. */ + const char *ssid = "network"; + if (e_iwd && e_iwd->manager) + { + const Eina_Hash *h = iwd_manager_networks(e_iwd->manager); + if (h) + { + Iwd_Network *n = eina_hash_find(h, netpath); + if (n && n->ssid) ssid = n->ssid; + } + } + const char *sec = NULL; + if (e_iwd && e_iwd->manager) + { + const Eina_Hash *h = iwd_manager_networks(e_iwd->manager); + if (h) + { + Iwd_Network *n = eina_hash_find(h, netpath); + if (n) sec = _sec_label(n->security); + } + } + _pending_dialog = wifi_auth_prompt(_popup ? _popup->box : e_comp->elm, + ssid, sec, _on_auth_done, NULL); } -/* Rescan button callback */ +/* ----- popup lifecycle ------------------------------------------------- */ + static void -_button_rescan_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +_destroy(void) { - Instance *inst = data; - - if (!inst || !inst->device) return; - - DBG("Rescan requested"); - iwd_device_scan(inst->device); - - /* Close and reopen popup to refresh */ - iwd_popup_del(inst); - ecore_timer_add(0.5, _popup_reopen_cb, inst); + if (!_popup) return; + if (_popup->listening && e_iwd && e_iwd->manager) + iwd_manager_listener_del(e_iwd->manager, _on_manager_change, _popup); + if (_popup->gp) e_object_del(E_OBJECT(_popup->gp)); + free(_popup); + _popup = NULL; } -/* Timer callback to reopen popup */ -static Eina_Bool -_popup_reopen_cb(void *data) +void +e_iwd_popup_close(void) { _destroy(); } + +void +e_iwd_popup_refresh(void) { if (_popup) _refresh(_popup); } + +void +e_iwd_popup_toggle(E_Gadcon_Client *gcc) { - Instance *inst = data; - if (inst) - iwd_popup_new(inst); - return ECORE_CALLBACK_CANCEL; -} - -/* Disconnect button callback */ -static void -_button_disconnect_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) -{ - Instance *inst = data; - - if (!inst || !inst->device) return; - - DBG("Disconnect requested"); - iwd_device_disconnect(inst->device); - - /* Close popup */ - iwd_popup_del(inst); -} - -/* Hidden network button callback */ -static void -_button_hidden_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) -{ - Instance *inst = data; - - if (!inst) return; - - DBG("Hidden network button clicked"); - - extern void wifi_hidden_dialog_show(Instance *inst); - wifi_hidden_dialog_show(inst); - - /* Close popup */ - iwd_popup_del(inst); -} - -/* Network selected callback */ -static void -_network_selected_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) -{ - IWD_Network *net = data; - - if (!net || !net->name) - { - DBG("Invalid network selected"); - return; - } - - INF("Network selected: %s (type: %s)", net->name, net->type ? net->type : "unknown"); - - /* Check if network requires authentication */ - if (net->type && (strcmp(net->type, "psk") == 0 || strcmp(net->type, "8021x") == 0)) - { - /* Secured network - need to show auth dialog first */ - /* Get instance from module */ - if (iwd_mod && iwd_mod->instances) - { - Instance *inst = eina_list_data_get(iwd_mod->instances); - if (inst) - { - extern void wifi_auth_dialog_show(Instance *inst, IWD_Network *net); - wifi_auth_dialog_show(inst, net); - } - } - } - else - { - /* Open network - connect directly */ - DBG("Connecting to open network"); - iwd_network_connect(net); - } + if (_popup) { _destroy(); return; } + if (!gcc || !e_iwd) return; + + Popup *p = calloc(1, sizeof(*p)); + _popup = p; + + p->gp = e_gadcon_popup_new(gcc, EINA_FALSE); + + Evas *evas = evas_object_evas_get(gcc->o_base); + Evas_Object *box = elm_box_add(e_comp->elm); + (void)evas; + elm_box_padding_set(box, 0, 4); + evas_object_size_hint_min_set(box, 240, 320); + p->box = box; + + /* Status line */ + Evas_Object *st = elm_label_add(box); + p->status_lbl = st; + elm_box_pack_end(box, st); + evas_object_show(st); + + /* Network list (vertical box of buttons — keeps deps minimal) */ + Evas_Object *scroller = elm_scroller_add(box); + evas_object_size_hint_weight_set(scroller, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(scroller, EVAS_HINT_FILL, EVAS_HINT_FILL); + Evas_Object *list_box = elm_box_add(scroller); + evas_object_size_hint_weight_set(list_box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(list_box, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_object_content_set(scroller, list_box); + evas_object_show(list_box); + elm_box_pack_end(box, scroller); + evas_object_show(scroller); + p->list = list_box; + + /* Action row */ + Evas_Object *row = elm_box_add(box); + elm_box_horizontal_set(row, EINA_TRUE); + elm_box_padding_set(row, 4, 0); + p->action_row = row; + + p->btn_scan = elm_button_add(row); + elm_object_text_set(p->btn_scan, "Rescan"); + evas_object_smart_callback_add(p->btn_scan, "clicked", _on_rescan, NULL); + elm_box_pack_end(row, p->btn_scan); evas_object_show(p->btn_scan); + + p->btn_toggle = elm_button_add(row); + elm_object_text_set(p->btn_toggle, "Disable"); + evas_object_smart_callback_add(p->btn_toggle, "clicked", _on_toggle, NULL); + elm_box_pack_end(row, p->btn_toggle); evas_object_show(p->btn_toggle); + + p->btn_hidden = elm_button_add(row); + elm_object_text_set(p->btn_hidden, "Hidden…"); + evas_object_smart_callback_add(p->btn_hidden, "clicked", _on_hidden, NULL); + elm_box_pack_end(row, p->btn_hidden); evas_object_show(p->btn_hidden); + + p->btn_disconnect = elm_button_add(row); + elm_object_text_set(p->btn_disconnect, "Disconnect"); + evas_object_smart_callback_add(p->btn_disconnect, "clicked", _on_disconnect, NULL); + elm_box_pack_end(row, p->btn_disconnect); + /* Visibility is driven by _refresh() based on connection state. */ + + elm_box_pack_end(box, row); + evas_object_show(row); + + evas_object_show(box); + e_gadcon_popup_content_set(p->gp, box); + e_gadcon_popup_show(p->gp); + + if (e_iwd->manager) + { + iwd_manager_listener_add(e_iwd->manager, _on_manager_change, p); + p->listening = EINA_TRUE; + iwd_manager_set_passphrase_handler(e_iwd->manager, _on_passphrase_request, NULL); + iwd_manager_scan_request(e_iwd->manager); + } + _refresh(p); } diff --git a/src/e_mod_popup.h b/src/e_mod_popup.h new file mode 100644 index 0000000..33717a9 --- /dev/null +++ b/src/e_mod_popup.h @@ -0,0 +1,11 @@ +#ifndef E_MOD_POPUP_H +#define E_MOD_POPUP_H + +#include + +void e_iwd_popup_install_passphrase_handler(void); +void e_iwd_popup_toggle (E_Gadcon_Client *gcc); +void e_iwd_popup_close (void); +void e_iwd_popup_refresh(void); + +#endif diff --git a/src/iwd/iwd_adapter.c b/src/iwd/iwd_adapter.c new file mode 100644 index 0000000..4747dad --- /dev/null +++ b/src/iwd/iwd_adapter.c @@ -0,0 +1,90 @@ +#include "iwd_adapter.h" +#include "iwd_dbus.h" +#include "iwd_props.h" +#include "iwd_manager.h" +#include +#include + +static void +_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v) +{ + Iwd_Adapter *a = data; + if (!strcmp(key, "Powered")) a->powered = iwd_props_bool(v); +} + +void +iwd_adapter_apply_props(Iwd_Adapter *a, Eldbus_Message_Iter *props) +{ + iwd_props_for_each(props, _prop_cb, a); +} + +static void +_on_props_changed(void *data, const Eldbus_Message *msg) +{ + Iwd_Adapter *a = data; + const char *iface; + Eldbus_Message_Iter *changed, *invalidated; + if (!eldbus_message_arguments_get(msg, "sa{sv}as", &iface, &changed, &invalidated)) + return; + if (strcmp(iface, IWD_IFACE_ADAPTER) != 0) return; + iwd_props_for_each(changed, _prop_cb, a); + if (a->manager) iwd_manager_notify(a->manager); +} + +Iwd_Adapter * +iwd_adapter_new(Eldbus_Connection *conn, const char *path, void *manager) +{ + Iwd_Adapter *a = calloc(1, sizeof(*a)); + if (!a) return NULL; + a->path = path ? strdup(path) : NULL; + a->manager = manager; + a->obj = eldbus_object_get(conn, IWD_BUS_NAME, path); + if (a->obj) + { + a->proxy = eldbus_proxy_get(a->obj, IWD_IFACE_ADAPTER); + if (a->proxy) + a->sh_props = eldbus_proxy_properties_changed_callback_add( + a->proxy, _on_props_changed, a); + } + return a; +} + +void +iwd_adapter_free(Iwd_Adapter *a) +{ + if (!a) return; + if (a->sh_props) eldbus_signal_handler_del(a->sh_props); + if (a->_props_proxy_keepalive) eldbus_proxy_unref(a->_props_proxy_keepalive); + if (a->proxy) eldbus_proxy_unref(a->proxy); + if (a->obj) eldbus_object_unref(a->obj); + free(a->path); + free(a); +} + +void +iwd_adapter_set_powered(Iwd_Adapter *a, Eina_Bool on) +{ + if (!a || !a->obj) return; + + /* Call org.freedesktop.DBus.Properties.Set explicitly so we control the + * variant marshaling exactly. eldbus_proxy_property_set silently swallows + * Adapter.Powered on this iwd version. */ + Eldbus_Proxy *props = eldbus_proxy_get(a->obj, "org.freedesktop.DBus.Properties"); + if (!props) return; + + Eldbus_Message *msg = eldbus_proxy_method_call_new(props, "Set"); + Eldbus_Message_Iter *iter = eldbus_message_iter_get(msg); + const char *iface = IWD_IFACE_ADAPTER; + const char *prop = "Powered"; + eldbus_message_iter_basic_append(iter, 's', iface); + eldbus_message_iter_basic_append(iter, 's', prop); + Eldbus_Message_Iter *variant = eldbus_message_iter_container_new(iter, 'v', "b"); + Eina_Bool v = on; + eldbus_message_iter_basic_append(variant, 'b', v); + eldbus_message_iter_container_close(iter, variant); + + eldbus_proxy_send(props, msg, NULL, NULL, -1); + /* Keep the props proxy alive on the adapter so the call isn't canceled. */ + if (a->_props_proxy_keepalive) eldbus_proxy_unref(a->_props_proxy_keepalive); + a->_props_proxy_keepalive = props; +} diff --git a/src/iwd/iwd_adapter.h b/src/iwd/iwd_adapter.h new file mode 100644 index 0000000..b62b007 --- /dev/null +++ b/src/iwd/iwd_adapter.h @@ -0,0 +1,24 @@ +#ifndef IWD_ADAPTER_H +#define IWD_ADAPTER_H + +#include +#include + +typedef struct _Iwd_Adapter +{ + char *path; + Eina_Bool powered; + Eldbus_Object *obj; + Eldbus_Proxy *proxy; + Eldbus_Proxy *_props_proxy_keepalive; + Eldbus_Signal_Handler *sh_props; + void *manager; +} Iwd_Adapter; + +Iwd_Adapter *iwd_adapter_new (Eldbus_Connection *conn, const char *path, void *manager); +void iwd_adapter_free(Iwd_Adapter *a); + +void iwd_adapter_apply_props(Iwd_Adapter *a, Eldbus_Message_Iter *props); +void iwd_adapter_set_powered(Iwd_Adapter *a, Eina_Bool on); + +#endif diff --git a/src/iwd/iwd_agent.c b/src/iwd/iwd_agent.c index 653f5b4..6947f90 100644 --- a/src/iwd/iwd_agent.c +++ b/src/iwd/iwd_agent.c @@ -1,319 +1,187 @@ #include "iwd_agent.h" #include "iwd_dbus.h" -#include "../e_mod_main.h" +#include +#include -/* Global agent */ -IWD_Agent *iwd_agent = NULL; +#define IWD_AGENT_PATH "/net/eiwd/agent" -/* Forward declarations */ -static Eldbus_Message *_agent_request_passphrase(const Eldbus_Service_Interface *iface, const Eldbus_Message *msg); -static Eldbus_Message *_agent_cancel(const Eldbus_Service_Interface *iface, const Eldbus_Message *msg); -static Eldbus_Message *_agent_release(const Eldbus_Service_Interface *iface, const Eldbus_Message *msg); -static void _agent_register_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending); -static void _agent_unregister(void); - -/* Agent interface methods */ -static const Eldbus_Method agent_methods[] = { - { - "RequestPassphrase", ELDBUS_ARGS({"o", "network"}), - ELDBUS_ARGS({"s", "passphrase"}), - _agent_request_passphrase, 0 - }, - { - "Cancel", ELDBUS_ARGS({"s", "reason"}), - NULL, - _agent_cancel, 0 - }, - { - "Release", NULL, NULL, - _agent_release, 0 - }, - { NULL, NULL, NULL, NULL, 0 } +struct _Iwd_Agent +{ + Eldbus_Connection *conn; + Eldbus_Service_Interface *svc; + Eldbus_Object *am_obj; + Eldbus_Proxy *am_proxy; + Iwd_Agent_Passphrase_Cb cb; + void *data; + Iwd_Agent_Cancel_Cb cancel_cb; + void *cancel_data; }; -/* Agent interface description */ -static const Eldbus_Service_Interface_Desc agent_desc = { - IWD_AGENT_MANAGER_INTERFACE, agent_methods, NULL, NULL, NULL, NULL +struct _Iwd_Agent_Request +{ + Iwd_Agent *agent; + Eldbus_Message *msg; /* original RequestPassphrase message */ }; -/* Initialize agent */ -Eina_Bool -iwd_agent_init(void) -{ - Eldbus_Connection *conn; - Eldbus_Object *obj; - Eldbus_Proxy *proxy; +static Iwd_Agent *_self = NULL; /* one agent per process is plenty */ - DBG("Initializing iwd agent"); +/* ----- Method handlers ------------------------------------------------- */ - if (iwd_agent) - { - WRN("Agent already initialized"); - return EINA_TRUE; - } - - conn = iwd_dbus_conn_get(); - if (!conn) - { - ERR("No D-Bus connection available"); - return EINA_FALSE; - } - - iwd_agent = E_NEW(IWD_Agent, 1); - if (!iwd_agent) - { - ERR("Failed to allocate agent"); - return EINA_FALSE; - } - - /* Initialize fields */ - iwd_agent->manager_obj = NULL; - iwd_agent->pending_network_path = NULL; - iwd_agent->pending_passphrase = NULL; - iwd_agent->pending_msg = NULL; - - /* Register D-Bus service interface */ - iwd_agent->iface = eldbus_service_interface_register(conn, IWD_AGENT_PATH, &agent_desc); - if (!iwd_agent->iface) - { - ERR("Failed to register agent interface"); - E_FREE(iwd_agent); - iwd_agent = NULL; - return EINA_FALSE; - } - - /* Register agent with iwd daemon (AgentManager is at /net/connman/iwd) */ - obj = eldbus_object_get(conn, IWD_SERVICE, IWD_DAEMON_PATH); - if (!obj) - { - ERR("Failed to get iwd daemon object"); - eldbus_service_interface_unregister(iwd_agent->iface); - E_FREE(iwd_agent); - iwd_agent = NULL; - return EINA_FALSE; - } - - proxy = eldbus_proxy_get(obj, IWD_AGENT_MANAGER_INTERFACE); - if (!proxy) - { - ERR("Failed to get AgentManager proxy"); - eldbus_object_unref(obj); - eldbus_service_interface_unregister(iwd_agent->iface); - E_FREE(iwd_agent); - iwd_agent = NULL; - return EINA_FALSE; - } - - /* Store object reference to keep it alive during async call */ - iwd_agent->manager_obj = obj; - - eldbus_proxy_call(proxy, "RegisterAgent", _agent_register_cb, NULL, -1, "o", IWD_AGENT_PATH); - - INF("Agent initialization started"); - return EINA_TRUE; -} - -/* Shutdown agent */ -void -iwd_agent_shutdown(void) -{ - DBG("Shutting down iwd agent"); - - if (!iwd_agent) return; - - _agent_unregister(); - - if (iwd_agent->iface) - eldbus_service_interface_unregister(iwd_agent->iface); - - if (iwd_agent->manager_obj) - eldbus_object_unref(iwd_agent->manager_obj); - - eina_stringshare_del(iwd_agent->pending_network_path); - eina_stringshare_del(iwd_agent->pending_passphrase); - - E_FREE(iwd_agent); - iwd_agent = NULL; -} - -/* Set passphrase for pending request and send reply */ -void -iwd_agent_set_passphrase(const char *passphrase) -{ - Eldbus_Message *reply; - - if (!iwd_agent) return; - if (!iwd_agent->pending_msg) - { - WRN("No pending passphrase request"); - return; - } - - DBG("Sending passphrase to iwd"); - - /* Create reply message */ - reply = eldbus_message_method_return_new(iwd_agent->pending_msg); - if (reply) - { - eldbus_message_arguments_append(reply, "s", passphrase); - eldbus_connection_send(eldbus_service_connection_get(iwd_agent->iface), - reply, NULL, NULL, -1); - } - - /* Clear pending request */ - eina_stringshare_del(iwd_agent->pending_network_path); - iwd_agent->pending_network_path = NULL; - iwd_agent->pending_msg = NULL; - - INF("Passphrase sent to iwd"); -} - -/* Cancel pending request */ -void -iwd_agent_cancel(void) -{ - Eldbus_Message *reply; - - if (!iwd_agent) return; - - /* Send cancellation reply if there's a pending request */ - if (iwd_agent->pending_msg) - { - reply = eldbus_message_error_new(iwd_agent->pending_msg, - "net.connman.iwd.Agent.Error.Canceled", - "User cancelled"); - if (reply) - { - eldbus_connection_send(eldbus_service_connection_get(iwd_agent->iface), - reply, NULL, NULL, -1); - } - } - - eina_stringshare_del(iwd_agent->pending_network_path); - eina_stringshare_del(iwd_agent->pending_passphrase); - iwd_agent->pending_network_path = NULL; - iwd_agent->pending_passphrase = NULL; - iwd_agent->pending_msg = NULL; - - DBG("Agent request cancelled"); -} - -/* Agent registration callback */ -static void -_agent_register_cb(void *data EINA_UNUSED, - const Eldbus_Message *msg, - Eldbus_Pending *pending EINA_UNUSED) -{ - const char *err_name, *err_msg; - - if (eldbus_message_error_get(msg, &err_name, &err_msg)) - { - ERR("Failed to register agent: %s: %s", err_name, err_msg); - return; - } - - INF("Agent registered with iwd"); -} - -/* Unregister agent */ -static void -_agent_unregister(void) -{ - Eldbus_Connection *conn; - Eldbus_Object *obj; - Eldbus_Proxy *proxy; - - conn = iwd_dbus_conn_get(); - if (!conn) return; - - obj = eldbus_object_get(conn, IWD_SERVICE, IWD_DAEMON_PATH); - if (!obj) return; - - proxy = eldbus_proxy_get(obj, IWD_AGENT_MANAGER_INTERFACE); - if (proxy) - { - eldbus_proxy_call(proxy, "UnregisterAgent", NULL, NULL, -1, "o", IWD_AGENT_PATH); - DBG("Agent unregistered from iwd"); - } - - eldbus_object_unref(obj); -} - -/* Request passphrase method */ static Eldbus_Message * -_agent_request_passphrase(const Eldbus_Service_Interface *iface EINA_UNUSED, - const Eldbus_Message *msg) +_release_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) { - const char *network_path; - IWD_Network *net; + return eldbus_message_method_return_new(msg); +} - if (!eldbus_message_arguments_get(msg, "o", &network_path)) - { - ERR("Failed to get network path from RequestPassphrase"); - return eldbus_message_error_new(msg, "org.freedesktop.DBus.Error.InvalidArgs", "Invalid arguments"); - } +static Eldbus_Message * +_cancel_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) +{ + /* iwd dropped the auth attempt; let the UI tear down its dialog. */ + const char *reason = NULL; + if (!eldbus_message_arguments_get(msg, "s", &reason)) reason = NULL; + if (_self && _self->cancel_cb) + _self->cancel_cb(_self->cancel_data, reason); + return eldbus_message_method_return_new(msg); +} - INF("Passphrase requested for network: %s", network_path); +/* iwd may also call these for EAP networks. We don't have UI for them yet, + * so politely refuse — that just fails the connect attempt instead of + * getting our agent unregistered. */ +static Eldbus_Message * +_unsupported_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) +{ + return eldbus_message_error_new(msg, + "net.connman.iwd.Agent.Error.Canceled", + "Method not supported by this agent"); +} - /* Store network path and message for later reply */ - eina_stringshare_replace(&iwd_agent->pending_network_path, network_path); - iwd_agent->pending_msg = msg; +static Eldbus_Message * +_request_passphrase_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) +{ + const char *path = NULL; + if (!eldbus_message_arguments_get(msg, "o", &path)) + return eldbus_message_error_new(msg, "net.connman.iwd.Error.InvalidArgs", + "Expected object path"); + if (!_self || !_self->cb) + return eldbus_message_error_new(msg, "net.connman.iwd.Agent.Error.Canceled", + "No UI handler"); - /* Find the network */ - net = iwd_network_find(network_path); - if (!net) - { - ERR("Network not found: %s", network_path); - iwd_agent->pending_msg = NULL; - return eldbus_message_error_new(msg, "net.connman.iwd.Agent.Error.Canceled", "Network not found"); - } - - /* Show passphrase dialog - this will eventually call iwd_agent_set_passphrase */ - /* We need to get the instance - for now, use the first one */ - if (iwd_mod && iwd_mod->instances) - { - Instance *inst = eina_list_data_get(iwd_mod->instances); - if (inst) - { - extern void wifi_auth_dialog_show(Instance *inst, IWD_Network *net); - wifi_auth_dialog_show(inst, net); - } - } - - /* Return NULL to indicate we'll reply later (async) */ + Iwd_Agent_Request *req = calloc(1, sizeof(*req)); + req->agent = _self; + req->msg = eldbus_message_ref((Eldbus_Message *)msg); + _self->cb(_self->data, req, path); + /* Deferred reply: returning NULL keeps the message pending. */ return NULL; } -/* Cancel method */ -static Eldbus_Message * -_agent_cancel(const Eldbus_Service_Interface *iface EINA_UNUSED, - const Eldbus_Message *msg) +static const Eldbus_Method _methods[] = { + { "Release", NULL, + NULL, _release_cb, 0 }, + { "RequestPassphrase", + ELDBUS_ARGS({ "o", "network" }), + ELDBUS_ARGS({ "s", "passphrase" }), + _request_passphrase_cb, 0 }, + { "Cancel", + ELDBUS_ARGS({ "s", "reason" }), + NULL, _cancel_cb, 0 }, + { "RequestPrivateKeyPassphrase", + ELDBUS_ARGS({ "o", "network" }), + ELDBUS_ARGS({ "s", "passphrase" }), + _unsupported_cb, 0 }, + { "RequestUserNameAndPassword", + ELDBUS_ARGS({ "o", "network" }), + ELDBUS_ARGS({ "s", "user" }, { "s", "password" }), + _unsupported_cb, 0 }, + { "RequestUserPassword", + ELDBUS_ARGS({ "o", "network" }, { "s", "user" }), + ELDBUS_ARGS({ "s", "password" }), + _unsupported_cb, 0 }, + { NULL, NULL, NULL, NULL, 0 } +}; + +static const Eldbus_Service_Interface_Desc _iface_desc = { + IWD_IFACE_AGENT, _methods, NULL, NULL, NULL, NULL +}; + +/* ----- Reply / cancel from the UI ------------------------------------- */ + +void +iwd_agent_reply(Iwd_Agent_Request *req, const char *passphrase) { - const char *reason; - - if (!eldbus_message_arguments_get(msg, "s", &reason)) - { - WRN("Cancel called with no reason"); - reason = "unknown"; - } - - INF("Agent request cancelled: %s", reason); - - iwd_agent_cancel(); - - /* TODO: Close passphrase dialog if open (Phase 4) */ - - return eldbus_message_method_return_new(msg); + if (!req) return; + Eldbus_Message *r = eldbus_message_method_return_new(req->msg); + eldbus_message_arguments_append(r, "s", passphrase ? passphrase : ""); + eldbus_connection_send(req->agent->conn, r, NULL, NULL, -1); + eldbus_message_unref(req->msg); + free(req); } -/* Release method */ -static Eldbus_Message * -_agent_release(const Eldbus_Service_Interface *iface EINA_UNUSED, - const Eldbus_Message *msg) +void +iwd_agent_cancel(Iwd_Agent_Request *req) { - INF("Agent released by iwd"); - - iwd_agent_cancel(); - - return eldbus_message_method_return_new(msg); + if (!req) return; + Eldbus_Message *e = eldbus_message_error_new(req->msg, + "net.connman.iwd.Agent.Error.Canceled", + "User canceled"); + eldbus_connection_send(req->agent->conn, e, NULL, NULL, -1); + eldbus_message_unref(req->msg); + free(req); +} + +/* ----- Registration with iwd ------------------------------------------ */ + +static void +_on_register(void *data EINA_UNUSED, const Eldbus_Message *msg, + Eldbus_Pending *p EINA_UNUSED) +{ + const char *en, *em; + if (eldbus_message_error_get(msg, &en, &em)) + fprintf(stderr, "e_iwd: agent register failed: %s: %s\n", en, em); +} + +Iwd_Agent * +iwd_agent_new(Eldbus_Connection *conn, Iwd_Agent_Passphrase_Cb cb, void *data) +{ + Iwd_Agent *a = calloc(1, sizeof(*a)); + if (!a) return NULL; + a->conn = conn; + a->cb = cb; + a->data = data; + _self = a; + + a->svc = eldbus_service_interface_register(conn, IWD_AGENT_PATH, &_iface_desc); + if (!a->svc) { free(a); _self = NULL; return NULL; } + + a->am_obj = eldbus_object_get(conn, IWD_BUS_NAME, "/net/connman/iwd"); + if (a->am_obj) + { + a->am_proxy = eldbus_proxy_get(a->am_obj, IWD_IFACE_AGENT_MANAGER); + if (a->am_proxy) + eldbus_proxy_call(a->am_proxy, "RegisterAgent", _on_register, NULL, -1, + "o", IWD_AGENT_PATH); + } + return a; +} + +void +iwd_agent_set_cancel_cb(Iwd_Agent *a, Iwd_Agent_Cancel_Cb cb, void *data) +{ + if (!a) return; + a->cancel_cb = cb; + a->cancel_data = data; +} + +void +iwd_agent_free(Iwd_Agent *a) +{ + if (!a) return; + if (a->svc) eldbus_service_interface_unregister(a->svc); + if (a->am_proxy) eldbus_proxy_unref(a->am_proxy); + if (a->am_obj) eldbus_object_unref(a->am_obj); + if (_self == a) _self = NULL; + free(a); } diff --git a/src/iwd/iwd_agent.h b/src/iwd/iwd_agent.h index 455f943..9fcc168 100644 --- a/src/iwd/iwd_agent.h +++ b/src/iwd/iwd_agent.h @@ -1,32 +1,29 @@ #ifndef IWD_AGENT_H #define IWD_AGENT_H -#include #include -#define IWD_AGENT_PATH "/org/enlightenment/eiwd/agent" +typedef struct _Iwd_Agent Iwd_Agent; +typedef struct _Iwd_Agent_Request Iwd_Agent_Request; -/* Agent structure */ -typedef struct _IWD_Agent -{ - Eldbus_Service_Interface *iface; - Eldbus_Object *manager_obj; /* Keep reference to prevent call cancellation */ - const char *pending_network_path; - const char *pending_passphrase; - const Eldbus_Message *pending_msg; /* Stored message to reply to */ -} IWD_Agent; +/* The UI registers a single handler that is called whenever iwd asks for + * a passphrase. The handler must eventually call iwd_agent_reply() or + * iwd_agent_cancel() with the request token. */ +typedef void (*Iwd_Agent_Passphrase_Cb)(void *data, + Iwd_Agent_Request *req, + const char *network_path); -/* Global agent */ -extern IWD_Agent *iwd_agent; +/* Fired when iwd issues a Cancel(reason) for the in-flight passphrase + * request — the UI should tear down any open auth dialog. */ +typedef void (*Iwd_Agent_Cancel_Cb)(void *data, const char *reason); -/* Agent management */ -Eina_Bool iwd_agent_init(void); -void iwd_agent_shutdown(void); +Iwd_Agent *iwd_agent_new (Eldbus_Connection *conn, + Iwd_Agent_Passphrase_Cb cb, void *data); -/* Set passphrase for pending request */ -void iwd_agent_set_passphrase(const char *passphrase); +void iwd_agent_set_cancel_cb(Iwd_Agent *a, Iwd_Agent_Cancel_Cb cb, void *data); +void iwd_agent_free(Iwd_Agent *a); -/* Cancel pending request */ -void iwd_agent_cancel(void); +void iwd_agent_reply (Iwd_Agent_Request *req, const char *passphrase); +void iwd_agent_cancel(Iwd_Agent_Request *req); #endif diff --git a/src/iwd/iwd_dbus.c b/src/iwd/iwd_dbus.c index b19f3a0..df9d764 100644 --- a/src/iwd/iwd_dbus.c +++ b/src/iwd/iwd_dbus.c @@ -1,398 +1,161 @@ #include "iwd_dbus.h" -#include "../e_mod_main.h" +#include +#include -/* Global D-Bus context */ -IWD_DBus *iwd_dbus = NULL; +#define FDO_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" -/* Forward declarations */ -static void _iwd_dbus_name_owner_changed_cb(void *data, const char *bus EINA_UNUSED, const char *old_id, const char *new_id); -static void _iwd_dbus_managed_objects_get_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending); -static void _iwd_dbus_interfaces_added_cb(void *data, const Eldbus_Message *msg); -static void _iwd_dbus_interfaces_removed_cb(void *data, const Eldbus_Message *msg); -static void _iwd_dbus_connect(void); -static void _iwd_dbus_disconnect(void); - -/* Initialize D-Bus connection */ -Eina_Bool -iwd_dbus_init(void) +struct _Iwd_Dbus { - DBG("Initializing iwd D-Bus connection"); + Eldbus_Connection *conn; + Iwd_Dbus_Callbacks cbs; + void *data; - if (iwd_dbus) - { - WRN("D-Bus already initialized"); - return EINA_TRUE; - } + Eldbus_Object *root_obj; + Eldbus_Proxy *root_om; + Eldbus_Signal_Handler *sh_added; + Eldbus_Signal_Handler *sh_removed; + Eina_Bool present; +}; - iwd_dbus = E_NEW(IWD_DBus, 1); - if (!iwd_dbus) - { - ERR("Failed to allocate D-Bus context"); - return EINA_FALSE; - } - - /* Connect to system bus */ - iwd_dbus->conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM); - if (!iwd_dbus->conn) - { - ERR("Failed to connect to system bus"); - E_FREE(iwd_dbus); - iwd_dbus = NULL; - return EINA_FALSE; - } - - /* Monitor iwd daemon availability */ - eldbus_name_owner_changed_callback_add(iwd_dbus->conn, - IWD_SERVICE, - _iwd_dbus_name_owner_changed_cb, - NULL, - EINA_TRUE); - - /* Try to connect to iwd */ - _iwd_dbus_connect(); - - return EINA_TRUE; -} - -/* Shutdown D-Bus connection */ -void -iwd_dbus_shutdown(void) -{ - DBG("Shutting down iwd D-Bus connection"); - - if (!iwd_dbus) return; - - _iwd_dbus_disconnect(); - - /* Unregister name owner changed callback */ - eldbus_name_owner_changed_callback_del(iwd_dbus->conn, IWD_SERVICE, - _iwd_dbus_name_owner_changed_cb, NULL); - - if (iwd_dbus->conn) - eldbus_connection_unref(iwd_dbus->conn); - - E_FREE(iwd_dbus); - iwd_dbus = NULL; -} - -/* Check if connected to iwd */ -Eina_Bool -iwd_dbus_is_connected(void) -{ - return (iwd_dbus && iwd_dbus->connected); -} - -/* Get D-Bus connection */ Eldbus_Connection * -iwd_dbus_conn_get(void) +iwd_dbus_conn(const Iwd_Dbus *d) { return d ? d->conn : NULL; } + +/* Walk the a{oa{sa{sv}}} reply from GetManagedObjects, emitting iface_added + * for every (path, interface) pair. */ +static void +_emit_managed(Iwd_Dbus *d, Eldbus_Message_Iter *objects) { - return iwd_dbus ? iwd_dbus->conn : NULL; + Eldbus_Message_Iter *entry; + while (eldbus_message_iter_get_and_next(objects, 'e', &entry)) + { + const char *path; + Eldbus_Message_Iter *ifaces; + if (!eldbus_message_iter_arguments_get(entry, "oa{sa{sv}}", &path, &ifaces)) + continue; + + Eldbus_Message_Iter *iface_entry; + while (eldbus_message_iter_get_and_next(ifaces, 'e', &iface_entry)) + { + const char *iface; + Eldbus_Message_Iter *props; + if (!eldbus_message_iter_arguments_get(iface_entry, "sa{sv}", &iface, &props)) + continue; + if (d->cbs.iface_added) + d->cbs.iface_added(d->data, path, iface, props); + } + } +} + +static void +_on_get_managed(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED) +{ + Iwd_Dbus *d = data; + if (eldbus_message_error_get(msg, NULL, NULL)) return; + Eldbus_Message_Iter *objects; + if (!eldbus_message_arguments_get(msg, "a{oa{sa{sv}}}", &objects)) return; + _emit_managed(d, objects); +} + +static void +_on_iface_added(void *data, const Eldbus_Message *msg) +{ + Iwd_Dbus *d = data; + const char *path; + Eldbus_Message_Iter *ifaces, *iface_entry; + if (!eldbus_message_arguments_get(msg, "oa{sa{sv}}", &path, &ifaces)) + return; + while (eldbus_message_iter_get_and_next(ifaces, 'e', &iface_entry)) + { + const char *iface; + Eldbus_Message_Iter *props; + if (!eldbus_message_iter_arguments_get(iface_entry, "sa{sv}", &iface, &props)) + continue; + if (d->cbs.iface_added) + d->cbs.iface_added(d->data, path, iface, props); + } +} + +static void +_on_iface_removed(void *data, const Eldbus_Message *msg) +{ + Iwd_Dbus *d = data; + const char *path; + Eldbus_Message_Iter *ifaces; + if (!eldbus_message_arguments_get(msg, "oas", &path, &ifaces)) + return; + const char *iface; + while (eldbus_message_iter_get_and_next(ifaces, 's', &iface)) + { + if (d->cbs.iface_removed) + d->cbs.iface_removed(d->data, path, iface); + } +} + +static void +_bind_root(Iwd_Dbus *d) +{ + if (d->root_om) return; + d->root_obj = eldbus_object_get(d->conn, IWD_BUS_NAME, "/"); + if (!d->root_obj) return; + d->root_om = eldbus_proxy_get(d->root_obj, FDO_OBJECT_MANAGER); + if (!d->root_om) return; + + d->sh_added = eldbus_proxy_signal_handler_add(d->root_om, "InterfacesAdded", + _on_iface_added, d); + d->sh_removed = eldbus_proxy_signal_handler_add(d->root_om, "InterfacesRemoved", + _on_iface_removed, d); + eldbus_proxy_call(d->root_om, "GetManagedObjects", _on_get_managed, d, -1, ""); +} + +static void +_unbind_root(Iwd_Dbus *d) +{ + if (d->sh_added) { eldbus_signal_handler_del(d->sh_added); d->sh_added = NULL; } + if (d->sh_removed) { eldbus_signal_handler_del(d->sh_removed); d->sh_removed = NULL; } + if (d->root_om) { eldbus_proxy_unref(d->root_om); d->root_om = NULL; } + if (d->root_obj) { eldbus_object_unref(d->root_obj); d->root_obj = NULL; } +} + +static void +_on_name_owner(void *data, const char *bus EINA_UNUSED, const char *old_id, const char *new_id) +{ + Iwd_Dbus *d = data; + Eina_Bool now = (new_id && new_id[0]); + Eina_Bool was = (old_id && old_id[0]); + + if (now && !d->present) + { + d->present = EINA_TRUE; + _bind_root(d); + if (d->cbs.name_appeared) d->cbs.name_appeared(d->data); + } + else if (!now && (was || d->present)) + { + d->present = EINA_FALSE; + _unbind_root(d); + if (d->cbs.name_vanished) d->cbs.name_vanished(d->data); + } +} + +Iwd_Dbus * +iwd_dbus_new(Eldbus_Connection *conn, const Iwd_Dbus_Callbacks *cbs, void *data) +{ + Iwd_Dbus *d = calloc(1, sizeof(*d)); + if (!d) return NULL; + d->conn = conn; + if (cbs) d->cbs = *cbs; + d->data = data; + + eldbus_name_owner_changed_callback_add(conn, IWD_BUS_NAME, + _on_name_owner, d, EINA_TRUE); + return d; } -/* Request managed objects from iwd */ void -iwd_dbus_get_managed_objects(void) +iwd_dbus_free(Iwd_Dbus *d) { - Eldbus_Proxy *proxy; - - if (!iwd_dbus || !iwd_dbus->manager_obj) return; - - DBG("Requesting managed objects from iwd"); - - proxy = eldbus_proxy_get(iwd_dbus->manager_obj, DBUS_OBJECT_MANAGER_INTERFACE); - if (!proxy) - { - ERR("Failed to get ObjectManager proxy"); - return; - } - - eldbus_proxy_call(proxy, "GetManagedObjects", - _iwd_dbus_managed_objects_get_cb, - NULL, -1, ""); -} - -/* Connect to iwd daemon */ -static void -_iwd_dbus_connect(void) -{ - if (!iwd_dbus || !iwd_dbus->conn) return; - - DBG("Connecting to iwd daemon"); - - /* Create manager object */ - iwd_dbus->manager_obj = eldbus_object_get(iwd_dbus->conn, IWD_SERVICE, IWD_MANAGER_PATH); - if (!iwd_dbus->manager_obj) - { - ERR("Failed to get manager object"); - return; - } - - /* Subscribe to ObjectManager signals */ - iwd_dbus->interfaces_added = - eldbus_proxy_signal_handler_add( - eldbus_proxy_get(iwd_dbus->manager_obj, DBUS_OBJECT_MANAGER_INTERFACE), - "InterfacesAdded", - _iwd_dbus_interfaces_added_cb, - NULL); - - iwd_dbus->interfaces_removed = - eldbus_proxy_signal_handler_add( - eldbus_proxy_get(iwd_dbus->manager_obj, DBUS_OBJECT_MANAGER_INTERFACE), - "InterfacesRemoved", - _iwd_dbus_interfaces_removed_cb, - NULL); - - iwd_dbus->connected = EINA_TRUE; - INF("Connected to iwd daemon"); - - /* Get initial state */ - iwd_dbus_get_managed_objects(); -} - -/* Disconnect from iwd daemon */ -static void -_iwd_dbus_disconnect(void) -{ - if (!iwd_dbus) return; - - DBG("Disconnecting from iwd daemon"); - - /* Unref the object first - this will clean up associated signal handlers */ - if (iwd_dbus->manager_obj) - { - eldbus_object_unref(iwd_dbus->manager_obj); - iwd_dbus->manager_obj = NULL; - } - - /* Clear handler pointers (they're already freed by object unref) */ - iwd_dbus->interfaces_added = NULL; - iwd_dbus->interfaces_removed = NULL; - - iwd_dbus->connected = EINA_FALSE; -} - -/* Name owner changed callback */ -static void -_iwd_dbus_name_owner_changed_cb(void *data EINA_UNUSED, - const char *bus EINA_UNUSED, - const char *old_id, - const char *new_id) -{ - DBG("iwd name owner changed: old='%s' new='%s'", old_id, new_id); - - if (new_id && new_id[0]) - { - /* iwd daemon started */ - INF("iwd daemon started - reconnecting"); - _iwd_dbus_connect(); - - /* Re-register agent */ - extern Eina_Bool iwd_agent_init(void); - iwd_agent_init(); - - /* Update state */ - extern void iwd_state_set(IWD_State state); - extern IWD_State iwd_state_get(void); - if (iwd_state_get() == IWD_STATE_ERROR) - { - iwd_state_set(IWD_STATE_IDLE); - } - } - else if (old_id && old_id[0]) - { - /* iwd daemon stopped */ - WRN("iwd daemon stopped"); - _iwd_dbus_disconnect(); - - /* Set error state */ - extern void iwd_state_set(IWD_State state); - iwd_state_set(IWD_STATE_ERROR); - - /* Show error dialog */ - e_util_dialog_show("IWD Wi-Fi Error", - "Wi-Fi daemon (iwd) has stopped.
" - "Please restart the iwd service."); - } -} - -/* Managed objects callback */ -static void -_iwd_dbus_managed_objects_get_cb(void *data EINA_UNUSED, - const Eldbus_Message *msg, - Eldbus_Pending *pending EINA_UNUSED) -{ - Eldbus_Message_Iter *array, *dict_entry; - const char *err_name, *err_msg; - - if (eldbus_message_error_get(msg, &err_name, &err_msg)) - { - ERR("Failed to get managed objects: %s: %s", err_name, err_msg); - return; - } - - if (!eldbus_message_arguments_get(msg, "a{oa{sa{sv}}}", &array)) - { - ERR("Failed to parse GetManagedObjects reply"); - return; - } - - DBG("Processing managed objects from iwd"); - - while (eldbus_message_iter_get_and_next(array, 'e', &dict_entry)) - { - Eldbus_Message_Iter *interfaces; - const char *path; - - eldbus_message_iter_arguments_get(dict_entry, "o", &path); - eldbus_message_iter_arguments_get(dict_entry, "a{sa{sv}}", &interfaces); - - DBG(" Object: %s", path); - - /* Parse interfaces and create device/network objects */ - Eldbus_Message_Iter *iface_entry; - Eina_Bool has_device = EINA_FALSE; - Eina_Bool has_station = EINA_FALSE; - Eina_Bool has_network = EINA_FALSE; - - while (eldbus_message_iter_get_and_next(interfaces, 'e', &iface_entry)) - { - const char *iface_name; - Eldbus_Message_Iter *properties; - - eldbus_message_iter_arguments_get(iface_entry, "s", &iface_name); - eldbus_message_iter_arguments_get(iface_entry, "a{sv}", &properties); - - DBG(" Interface: %s", iface_name); - - if (strcmp(iface_name, IWD_DEVICE_INTERFACE) == 0) - has_device = EINA_TRUE; - else if (strcmp(iface_name, IWD_STATION_INTERFACE) == 0) - has_station = EINA_TRUE; - else if (strcmp(iface_name, IWD_NETWORK_INTERFACE) == 0) - has_network = EINA_TRUE; - } - - /* Create device if it has Device and Station interfaces */ - if (has_device && has_station) - { - extern IWD_Device *iwd_device_new(const char *path); - IWD_Device *dev = iwd_device_new(path); - if (dev) - { - /* Parse properties - re-iterate to get them */ - eldbus_message_iter_arguments_get(dict_entry, "o", &path); - eldbus_message_iter_arguments_get(dict_entry, "a{sa{sv}}", &interfaces); - - while (eldbus_message_iter_get_and_next(interfaces, 'e', &iface_entry)) - { - const char *iface_name; - Eldbus_Message_Iter *properties; - - eldbus_message_iter_arguments_get(iface_entry, "s", &iface_name); - eldbus_message_iter_arguments_get(iface_entry, "a{sv}", &properties); - - if (strcmp(iface_name, IWD_DEVICE_INTERFACE) == 0 || - strcmp(iface_name, IWD_STATION_INTERFACE) == 0) - { - extern void _device_parse_properties(IWD_Device *dev, Eldbus_Message_Iter *properties); - /* We need to expose the parse function or call it via a public function */ - /* For now, properties will be updated via PropertyChanged signals */ - } - } - } - } - - /* Create network if it has Network interface */ - if (has_network) - { - extern IWD_Network *iwd_network_new(const char *path); - iwd_network_new(path); - } - } -} - -/* Interfaces added callback */ -static void -_iwd_dbus_interfaces_added_cb(void *data EINA_UNUSED, - const Eldbus_Message *msg) -{ - const char *path; - Eldbus_Message_Iter *interfaces; - - if (!eldbus_message_arguments_get(msg, "oa{sa{sv}}", &path, &interfaces)) - { - ERR("Failed to parse InterfacesAdded signal"); - return; - } - - DBG("Interfaces added at: %s", path); - - /* Check what interfaces were added */ - Eldbus_Message_Iter *iface_entry; - Eina_Bool has_device = EINA_FALSE; - Eina_Bool has_station = EINA_FALSE; - Eina_Bool has_network = EINA_FALSE; - - while (eldbus_message_iter_get_and_next(interfaces, 'e', &iface_entry)) - { - const char *iface_name; - Eldbus_Message_Iter *properties; - - eldbus_message_iter_arguments_get(iface_entry, "s", &iface_name); - eldbus_message_iter_arguments_get(iface_entry, "a{sv}", &properties); - - if (strcmp(iface_name, IWD_DEVICE_INTERFACE) == 0) - has_device = EINA_TRUE; - else if (strcmp(iface_name, IWD_STATION_INTERFACE) == 0) - has_station = EINA_TRUE; - else if (strcmp(iface_name, IWD_NETWORK_INTERFACE) == 0) - has_network = EINA_TRUE; - } - - if (has_device && has_station) - { - extern IWD_Device *iwd_device_new(const char *path); - iwd_device_new(path); - } - - if (has_network) - { - extern IWD_Network *iwd_network_new(const char *path); - iwd_network_new(path); - } -} - -/* Interfaces removed callback */ -static void -_iwd_dbus_interfaces_removed_cb(void *data EINA_UNUSED, - const Eldbus_Message *msg) -{ - const char *path; - Eldbus_Message_Iter *interfaces; - - if (!eldbus_message_arguments_get(msg, "oas", &path, &interfaces)) - { - ERR("Failed to parse InterfacesRemoved signal"); - return; - } - - DBG("Interfaces removed at: %s", path); - - /* Check if we should remove device or network */ - extern IWD_Device *iwd_device_find(const char *path); - extern IWD_Network *iwd_network_find(const char *path); - extern void iwd_device_free(IWD_Device *dev); - extern void iwd_network_free(IWD_Network *net); - - IWD_Device *dev = iwd_device_find(path); - if (dev) - { - iwd_device_free(dev); - } - - IWD_Network *net = iwd_network_find(path); - if (net) - { - iwd_network_free(net); - } + if (!d) return; + eldbus_name_owner_changed_callback_del(d->conn, IWD_BUS_NAME, _on_name_owner, d); + _unbind_root(d); + free(d); } diff --git a/src/iwd/iwd_dbus.h b/src/iwd/iwd_dbus.h index 0078350..e97a8fe 100644 --- a/src/iwd/iwd_dbus.h +++ b/src/iwd/iwd_dbus.h @@ -1,50 +1,35 @@ #ifndef IWD_DBUS_H #define IWD_DBUS_H -#include #include +#include -/* iwd D-Bus service and interfaces */ -#define IWD_SERVICE "net.connman.iwd" -#define IWD_MANAGER_PATH "/" -#define IWD_DAEMON_PATH "/net/connman/iwd" -#define IWD_MANAGER_INTERFACE "net.connman.iwd.Manager" -#define IWD_ADAPTER_INTERFACE "net.connman.iwd.Adapter" -#define IWD_DEVICE_INTERFACE "net.connman.iwd.Device" -#define IWD_STATION_INTERFACE "net.connman.iwd.Station" -#define IWD_NETWORK_INTERFACE "net.connman.iwd.Network" -#define IWD_KNOWN_NETWORK_INTERFACE "net.connman.iwd.KnownNetwork" -#define IWD_AGENT_MANAGER_INTERFACE "net.connman.iwd.AgentManager" +#define IWD_BUS_NAME "net.connman.iwd" +#define IWD_IFACE_ADAPTER "net.connman.iwd.Adapter" +#define IWD_IFACE_DEVICE "net.connman.iwd.Device" +#define IWD_IFACE_STATION "net.connman.iwd.Station" +#define IWD_IFACE_NETWORK "net.connman.iwd.Network" +#define IWD_IFACE_KNOWN_NETWORK "net.connman.iwd.KnownNetwork" +#define IWD_IFACE_AGENT_MANAGER "net.connman.iwd.AgentManager" +#define IWD_IFACE_AGENT "net.connman.iwd.Agent" -#define DBUS_OBJECT_MANAGER_INTERFACE "org.freedesktop.DBus.ObjectManager" -#define DBUS_PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties" +typedef struct _Iwd_Dbus Iwd_Dbus; -/* D-Bus context */ -typedef struct _IWD_DBus +/* Callbacks the consumer (iwd_manager) registers to react to bus state. */ +typedef struct _Iwd_Dbus_Callbacks { - Eldbus_Connection *conn; - Eldbus_Object *manager_obj; - Eldbus_Proxy *manager_proxy; - Eldbus_Signal_Handler *interfaces_added; - Eldbus_Signal_Handler *interfaces_removed; + void (*name_appeared) (void *data); + void (*name_vanished) (void *data); + /* iface_props is an iter positioned on a{sv} for the given interface. */ + void (*iface_added) (void *data, const char *path, const char *iface, + Eldbus_Message_Iter *iface_props); + void (*iface_removed) (void *data, const char *path, const char *iface); +} Iwd_Dbus_Callbacks; - Eina_Bool connected; -} IWD_DBus; +Iwd_Dbus *iwd_dbus_new (Eldbus_Connection *conn, + const Iwd_Dbus_Callbacks *cbs, void *data); +void iwd_dbus_free(Iwd_Dbus *d); -/* Global D-Bus context */ -extern IWD_DBus *iwd_dbus; - -/* Initialization and shutdown */ -Eina_Bool iwd_dbus_init(void); -void iwd_dbus_shutdown(void); - -/* Connection state */ -Eina_Bool iwd_dbus_is_connected(void); - -/* Helper to get D-Bus connection */ -Eldbus_Connection *iwd_dbus_conn_get(void); - -/* Get managed objects */ -void iwd_dbus_get_managed_objects(void); +Eldbus_Connection *iwd_dbus_conn(const Iwd_Dbus *d); #endif diff --git a/src/iwd/iwd_device.c b/src/iwd/iwd_device.c index a3c17ee..1c05c52 100644 --- a/src/iwd/iwd_device.c +++ b/src/iwd/iwd_device.c @@ -1,290 +1,220 @@ #include "iwd_device.h" #include "iwd_dbus.h" -#include "../e_mod_main.h" +#include "iwd_props.h" +#include "iwd_manager.h" +#include "iwd_network.h" +#include +#include +#include -/* Global device list */ -Eina_List *iwd_devices = NULL; +static void _refresh_signals(Iwd_Device *d); -/* Forward declarations */ -static void _device_properties_changed_cb(void *data, const Eldbus_Message *msg); -static void _device_parse_properties(IWD_Device *dev, Eldbus_Message_Iter *properties); - -/* Create new device */ -IWD_Device * -iwd_device_new(const char *path) +static Iwd_Station_State +_state_from_str(const char *s) { - IWD_Device *dev; - Eldbus_Connection *conn; - Eldbus_Object *obj; - - if (!path) return NULL; - - conn = iwd_dbus_conn_get(); - if (!conn) return NULL; - - /* Check if device already exists */ - dev = iwd_device_find(path); - if (dev) - { - DBG("Device already exists: %s", path); - return dev; - } - - DBG("Creating new device: %s", path); - - dev = E_NEW(IWD_Device, 1); - if (!dev) return NULL; - - dev->path = eina_stringshare_add(path); - - /* Create D-Bus object */ - obj = eldbus_object_get(conn, IWD_SERVICE, path); - if (!obj) - { - ERR("Failed to get D-Bus object for device"); - eina_stringshare_del(dev->path); - E_FREE(dev); - return NULL; - } - - /* Get proxies */ - dev->device_proxy = eldbus_proxy_get(obj, IWD_DEVICE_INTERFACE); - dev->station_proxy = eldbus_proxy_get(obj, IWD_STATION_INTERFACE); - - /* Subscribe to property changes */ - dev->properties_changed = - eldbus_proxy_signal_handler_add(dev->station_proxy, - "PropertiesChanged", - _device_properties_changed_cb, - dev); - - /* Add to global list */ - iwd_devices = eina_list_append(iwd_devices, dev); - - INF("Created device: %s", path); - - eldbus_object_unref(obj); - return dev; + if (!s) return IWD_STATION_DISCONNECTED; + if (!strcmp(s, "connected")) return IWD_STATION_CONNECTED; + if (!strcmp(s, "connecting")) return IWD_STATION_CONNECTING; + if (!strcmp(s, "disconnecting")) return IWD_STATION_DISCONNECTING; + if (!strcmp(s, "roaming")) return IWD_STATION_ROAMING; + return IWD_STATION_DISCONNECTED; } -/* Free device */ -void -iwd_device_free(IWD_Device *dev) -{ - if (!dev) return; - - DBG("Freeing device: %s", dev->path); - - iwd_devices = eina_list_remove(iwd_devices, dev); - - if (dev->properties_changed) - eldbus_signal_handler_del(dev->properties_changed); - - eina_stringshare_del(dev->path); - eina_stringshare_del(dev->name); - eina_stringshare_del(dev->address); - eina_stringshare_del(dev->adapter_path); - eina_stringshare_del(dev->mode); - eina_stringshare_del(dev->state); - eina_stringshare_del(dev->connected_network); - - E_FREE(dev); -} - -/* Find device by path */ -IWD_Device * -iwd_device_find(const char *path) -{ - Eina_List *l; - IWD_Device *dev; - - if (!path) return NULL; - - EINA_LIST_FOREACH(iwd_devices, l, dev) - { - if (dev->path && strcmp(dev->path, path) == 0) - return dev; - } - - return NULL; -} - -/* Trigger scan */ -void -iwd_device_scan(IWD_Device *dev) -{ - if (!dev || !dev->station_proxy) - { - ERR("Invalid device for scan"); - return; - } - - DBG("Triggering scan on device: %s", dev->name ? dev->name : dev->path); - - eldbus_proxy_call(dev->station_proxy, "Scan", NULL, NULL, -1, ""); -} - -/* Disconnect from network */ -void -iwd_device_disconnect(IWD_Device *dev) -{ - if (!dev || !dev->station_proxy) - { - ERR("Invalid device for disconnect"); - return; - } - - DBG("Disconnecting device: %s", dev->name ? dev->name : dev->path); - - eldbus_proxy_call(dev->station_proxy, "Disconnect", NULL, NULL, -1, ""); -} - -/* Connect to hidden network */ -void -iwd_device_connect_hidden(IWD_Device *dev, const char *ssid) -{ - if (!dev || !dev->station_proxy || !ssid) - { - ERR("Invalid parameters for hidden network connect"); - return; - } - - DBG("Connecting to hidden network: %s", ssid); - - eldbus_proxy_call(dev->station_proxy, "ConnectHiddenNetwork", - NULL, NULL, -1, "s", ssid); -} - -/* Get devices list */ -Eina_List * -iwd_devices_get(void) -{ - return iwd_devices; -} - -/* Initialize device subsystem */ -void -iwd_device_init(void) -{ - DBG("Initializing device subsystem"); - /* Devices will be populated from ObjectManager signals */ -} - -/* Shutdown device subsystem */ -void -iwd_device_shutdown(void) -{ - IWD_Device *dev; - - DBG("Shutting down device subsystem"); - - EINA_LIST_FREE(iwd_devices, dev) - iwd_device_free(dev); -} - -/* Properties changed callback */ static void -_device_properties_changed_cb(void *data, - const Eldbus_Message *msg) +_dev_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v) { - IWD_Device *dev = data; - const char *interface; + Iwd_Device *d = data; + if (!strcmp(key, "Name")) { free(d->name); d->name = iwd_props_str_dup(v); } + else if (!strcmp(key, "Address")) { free(d->address); d->address = iwd_props_str_dup(v); } + else if (!strcmp(key, "Adapter")) { free(d->adapter_path); d->adapter_path = iwd_props_str_dup(v); } + else if (!strcmp(key, "Powered")) { d->powered = iwd_props_bool(v); } +} + +static void +_sta_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v) +{ + Iwd_Device *d = data; + if (!strcmp(key, "State")) + { + char *s = iwd_props_str_dup(v); + d->station_state = _state_from_str(s); + free(s); + } + else if (!strcmp(key, "Scanning")) + { + Eina_Bool was = d->scanning; + d->scanning = iwd_props_bool(v); + /* When a scan finishes, ask iwd for the ranked list with RSSI. */ + if (was && !d->scanning) _refresh_signals(d); + } + else if (!strcmp(key, "ConnectedNetwork")) { free(d->connected_network); d->connected_network = iwd_props_str_dup(v); } +} + +void +iwd_device_apply_device_props(Iwd_Device *d, Eldbus_Message_Iter *props) +{ + iwd_props_for_each(props, _dev_prop_cb, d); +} + +void +iwd_device_apply_station_props(Iwd_Device *d, Eldbus_Message_Iter *props) +{ + d->has_station = EINA_TRUE; + iwd_props_for_each(props, _sta_prop_cb, d); +} + +/* org.freedesktop.DBus.Properties.PropertiesChanged: (s, a{sv}, as) */ +static void +_on_dev_props_changed(void *data, const Eldbus_Message *msg) +{ + Iwd_Device *d = data; + const char *iface; Eldbus_Message_Iter *changed, *invalidated; - - if (!eldbus_message_arguments_get(msg, "sa{sv}as", &interface, &changed, &invalidated)) - { - ERR("Failed to parse PropertiesChanged signal"); - return; - } - - DBG("Properties changed for device %s on interface %s", dev->path, interface); - - _device_parse_properties(dev, changed); - - /* Update global state from device */ - extern void iwd_state_update_from_device(IWD_Device *dev); - iwd_state_update_from_device(dev); + if (!eldbus_message_arguments_get(msg, "sa{sv}as", &iface, &changed, &invalidated)) + return; + if (strcmp(iface, IWD_IFACE_DEVICE) != 0) return; + iwd_props_for_each(changed, _dev_prop_cb, d); + if (d->manager) iwd_manager_notify(d->manager); } -/* Parse device properties */ static void -_device_parse_properties(IWD_Device *dev, - Eldbus_Message_Iter *properties) +_on_sta_props_changed(void *data, const Eldbus_Message *msg) { - Eldbus_Message_Iter *entry; - - if (!properties) return; - - while (eldbus_message_iter_get_and_next(properties, 'e', &entry)) - { - const char *key; - Eldbus_Message_Iter *var; - - if (!eldbus_message_iter_arguments_get(entry, "sv", &key, &var)) - continue; - - if (strcmp(key, "Name") == 0) - { - const char *name; - if (eldbus_message_iter_arguments_get(var, "s", &name)) - { - eina_stringshare_replace(&dev->name, name); - DBG(" Name: %s", dev->name); - } - } - else if (strcmp(key, "Address") == 0) - { - const char *address; - if (eldbus_message_iter_arguments_get(var, "s", &address)) - { - eina_stringshare_replace(&dev->address, address); - DBG(" Address: %s", dev->address); - } - } - else if (strcmp(key, "Powered") == 0) - { - Eina_Bool powered; - if (eldbus_message_iter_arguments_get(var, "b", &powered)) - { - dev->powered = powered; - DBG(" Powered: %d", dev->powered); - } - } - else if (strcmp(key, "Scanning") == 0) - { - Eina_Bool scanning; - if (eldbus_message_iter_arguments_get(var, "b", &scanning)) - { - dev->scanning = scanning; - DBG(" Scanning: %d", dev->scanning); - } - } - else if (strcmp(key, "State") == 0) - { - const char *state; - if (eldbus_message_iter_arguments_get(var, "s", &state)) - { - eina_stringshare_replace(&dev->state, state); - DBG(" State: %s", dev->state); - } - } - else if (strcmp(key, "ConnectedNetwork") == 0) - { - const char *network; - if (eldbus_message_iter_arguments_get(var, "o", &network)) - { - eina_stringshare_replace(&dev->connected_network, network); - DBG(" Connected network: %s", dev->connected_network); - } - } - else if (strcmp(key, "Mode") == 0) - { - const char *mode; - if (eldbus_message_iter_arguments_get(var, "s", &mode)) - { - eina_stringshare_replace(&dev->mode, mode); - DBG(" Mode: %s", dev->mode); - } - } - } + Iwd_Device *d = data; + const char *iface; + Eldbus_Message_Iter *changed, *invalidated; + if (!eldbus_message_arguments_get(msg, "sa{sv}as", &iface, &changed, &invalidated)) + return; + if (strcmp(iface, IWD_IFACE_STATION) != 0) return; + iwd_props_for_each(changed, _sta_prop_cb, d); + if (d->manager) iwd_manager_notify(d->manager); +} + +Iwd_Device * +iwd_device_new(Eldbus_Connection *conn, const char *path, void *manager) +{ + Iwd_Device *d = calloc(1, sizeof(*d)); + if (!d) return NULL; + d->path = path ? strdup(path) : NULL; + d->manager = manager; + d->obj = eldbus_object_get(conn, IWD_BUS_NAME, path); + if (d->obj) + { + d->device_proxy = eldbus_proxy_get(d->obj, IWD_IFACE_DEVICE); + if (d->device_proxy) + d->sh_dev_props = eldbus_proxy_properties_changed_callback_add( + d->device_proxy, _on_dev_props_changed, d); + } + return d; +} + +void +iwd_device_attach_station(Iwd_Device *d) +{ + if (!d || d->station_proxy || !d->obj) return; + d->station_proxy = eldbus_proxy_get(d->obj, IWD_IFACE_STATION); + if (d->station_proxy) + { + d->has_station = EINA_TRUE; + d->sh_sta_props = eldbus_proxy_properties_changed_callback_add( + d->station_proxy, _on_sta_props_changed, d); + _refresh_signals(d); + } +} + +void +iwd_device_detach_station(Iwd_Device *d) +{ + if (!d) return; + if (d->sh_sta_props) { eldbus_signal_handler_del(d->sh_sta_props); d->sh_sta_props = NULL; } + if (d->station_proxy) { eldbus_proxy_unref(d->station_proxy); d->station_proxy = NULL; } + d->has_station = EINA_FALSE; +} + +void +iwd_device_free(Iwd_Device *d) +{ + if (!d) return; + iwd_device_detach_station(d); + if (d->sh_dev_props) eldbus_signal_handler_del(d->sh_dev_props); + if (d->device_proxy) eldbus_proxy_unref(d->device_proxy); + if (d->obj) eldbus_object_unref(d->obj); + free(d->path); + free(d->name); + free(d->address); + free(d->adapter_path); + free(d->connected_network); + free(d); +} + +/* Reply to Station.GetOrderedNetworks: a(on) — list of (object_path, RSSI). + * RSSI is a 16-bit signed value in 100*dBm units. */ +static void +_on_ordered_networks(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) +{ + Iwd_Device *d = data; + const char *en, *em; + if (eldbus_message_error_get(msg, &en, &em)) return; + + Eldbus_Message_Iter *array = NULL; + if (!eldbus_message_arguments_get(msg, "a(on)", &array) || !array) + return; + + const Eina_Hash *nets = d->manager ? iwd_manager_networks(d->manager) : NULL; + Eldbus_Message_Iter *entry; + Eina_Bool any = EINA_FALSE; + while (eldbus_message_iter_get_and_next(array, 'r', &entry)) + { + const char *path = NULL; + int16_t rssi = 0; + if (!eldbus_message_iter_arguments_get(entry, "on", &path, &rssi)) continue; + if (!nets || !path) continue; + Iwd_Network *n = eina_hash_find(nets, path); + if (!n) continue; + n->signal_dbm = rssi; + n->have_signal = EINA_TRUE; + any = EINA_TRUE; + } + if (any && d->manager) iwd_manager_notify(d->manager); +} + +static void +_refresh_signals(Iwd_Device *d) +{ + if (!d || !d->station_proxy) return; + eldbus_proxy_call(d->station_proxy, "GetOrderedNetworks", + _on_ordered_networks, d, -1, ""); +} + +void +iwd_device_scan(Iwd_Device *d) +{ + if (!d || !d->station_proxy) return; + eldbus_proxy_call(d->station_proxy, "Scan", NULL, NULL, -1, ""); +} + +void +iwd_device_disconnect(Iwd_Device *d) +{ + if (!d || !d->station_proxy) return; + eldbus_proxy_call(d->station_proxy, "Disconnect", NULL, NULL, -1, ""); +} + +static void +_on_connect_hidden_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) +{ + const char *en, *em; + char *ssid = data; + if (eldbus_message_error_get(msg, &en, &em)) + fprintf(stderr, "e_iwd: ConnectHiddenNetwork('%s') failed: %s: %s\n", + ssid ? ssid : "?", en, em); + free(ssid); +} + +void +iwd_device_connect_hidden(Iwd_Device *d, const char *ssid) +{ + if (!d || !d->station_proxy || !ssid || !*ssid) return; + eldbus_proxy_call(d->station_proxy, "ConnectHiddenNetwork", + _on_connect_hidden_reply, strdup(ssid), -1, "s", ssid); } diff --git a/src/iwd/iwd_device.h b/src/iwd/iwd_device.h index 6ae886d..27606c6 100644 --- a/src/iwd/iwd_device.h +++ b/src/iwd/iwd_device.h @@ -4,45 +4,49 @@ #include #include -/* Device structure */ -typedef struct _IWD_Device +typedef struct _Iwd_Device Iwd_Device; + +typedef enum { + IWD_STATION_DISCONNECTED, + IWD_STATION_CONNECTING, + IWD_STATION_CONNECTED, + IWD_STATION_DISCONNECTING, + IWD_STATION_ROAMING, +} Iwd_Station_State; + +struct _Iwd_Device { - const char *path; /* D-Bus object path */ - const char *name; /* Interface name (e.g., "wlan0") */ - const char *address; /* MAC address */ - const char *adapter_path; /* Adapter object path */ - const char *mode; /* "station", "ap", "ad-hoc" */ - Eina_Bool powered; /* Device powered state */ + char *path; + char *name; + char *address; + char *adapter_path; + Eina_Bool powered; - /* Station interface properties */ - Eina_Bool scanning; - const char *state; /* "disconnected", "connecting", "connected", "disconnecting" */ - const char *connected_network; /* Connected network object path */ + /* Station-side state, valid when station_proxy != NULL */ + Eina_Bool has_station; + Iwd_Station_State station_state; + Eina_Bool scanning; + char *connected_network; - /* D-Bus objects */ - Eldbus_Proxy *device_proxy; - Eldbus_Proxy *station_proxy; - Eldbus_Signal_Handler *properties_changed; -} IWD_Device; + Eldbus_Object *obj; + Eldbus_Proxy *device_proxy; + Eldbus_Proxy *station_proxy; + Eldbus_Signal_Handler *sh_dev_props; + Eldbus_Signal_Handler *sh_sta_props; -/* Global device list */ -extern Eina_List *iwd_devices; + void *manager; /* back-ref, opaque (Iwd_Manager *) */ +}; -/* Device management */ -IWD_Device *iwd_device_new(const char *path); -void iwd_device_free(IWD_Device *dev); -IWD_Device *iwd_device_find(const char *path); +Iwd_Device *iwd_device_new (Eldbus_Connection *conn, const char *path, void *manager); +void iwd_device_free(Iwd_Device *d); -/* Device operations */ -void iwd_device_scan(IWD_Device *dev); -void iwd_device_disconnect(IWD_Device *dev); -void iwd_device_connect_hidden(IWD_Device *dev, const char *ssid); +void iwd_device_apply_device_props (Iwd_Device *d, Eldbus_Message_Iter *props); +void iwd_device_apply_station_props(Iwd_Device *d, Eldbus_Message_Iter *props); +void iwd_device_attach_station (Iwd_Device *d); +void iwd_device_detach_station (Iwd_Device *d); -/* Get devices list */ -Eina_List *iwd_devices_get(void); - -/* Initialize device subsystem */ -void iwd_device_init(void); -void iwd_device_shutdown(void); +void iwd_device_scan (Iwd_Device *d); +void iwd_device_disconnect (Iwd_Device *d); +void iwd_device_connect_hidden (Iwd_Device *d, const char *ssid); #endif diff --git a/src/iwd/iwd_manager.c b/src/iwd/iwd_manager.c new file mode 100644 index 0000000..3332d69 --- /dev/null +++ b/src/iwd/iwd_manager.c @@ -0,0 +1,297 @@ +#include "iwd_manager.h" +#include "iwd_dbus.h" +#include "iwd_adapter.h" +#include "iwd_device.h" +#include "iwd_network.h" +#include +#include + +typedef struct _Listener +{ + Iwd_Manager_Cb cb; + void *data; +} Listener; + +struct _Iwd_Manager +{ + Iwd_Dbus *dbus; + Iwd_Agent *agent; + Eina_Hash *adapters; /* path → Iwd_Adapter * */ + Eina_Hash *devices; /* path → Iwd_Device * */ + Eina_Hash *networks; /* path → Iwd_Network * */ + Eina_List *listeners; /* Listener * */ + Iwd_State state; + Eina_Bool notify_pending; + + Iwd_Agent_Passphrase_Cb pass_cb; + void *pass_data; +}; + +static void +_passphrase_trampoline(void *data, Iwd_Agent_Request *req, const char *path) +{ + Iwd_Manager *m = data; + if (m->pass_cb) m->pass_cb(m->pass_data, req, path); + else iwd_agent_cancel(req); +} + +void +iwd_manager_set_passphrase_handler(Iwd_Manager *m, Iwd_Agent_Passphrase_Cb cb, void *data) +{ + if (!m) return; + m->pass_cb = cb; + m->pass_data = data; +} + +void +iwd_manager_set_cancel_handler(Iwd_Manager *m, Iwd_Agent_Cancel_Cb cb, void *data) +{ + if (!m) return; + iwd_agent_set_cancel_cb(m->agent, cb, data); +} + +static void _recompute_state(Iwd_Manager *m); + +/* ----- listeners ------------------------------------------------------- */ + +void +iwd_manager_listener_add(Iwd_Manager *m, Iwd_Manager_Cb cb, void *data) +{ + if (!m || !cb) return; + Listener *l = calloc(1, sizeof(*l)); + l->cb = cb; l->data = data; + m->listeners = eina_list_append(m->listeners, l); +} + +void +iwd_manager_listener_del(Iwd_Manager *m, Iwd_Manager_Cb cb, void *data) +{ + if (!m) return; + Eina_List *l; + Listener *li; + EINA_LIST_FOREACH(m->listeners, l, li) + if (li->cb == cb && li->data == data) + { + m->listeners = eina_list_remove_list(m->listeners, l); + free(li); + return; + } +} + +void +iwd_manager_notify(Iwd_Manager *m) +{ + if (!m) return; + _recompute_state(m); + Eina_List *l; + Listener *li; + EINA_LIST_FOREACH(m->listeners, l, li) + li->cb(li->data, m); +} + +/* ----- state aggregation ---------------------------------------------- */ + +static void +_recompute_state(Iwd_Manager *m) +{ + Iwd_State s = IWD_STATE_OFF; + Eina_Bool any_powered = EINA_FALSE; + + /* Adapter.Powered is the source of truth for radio state — Device.Powered + * is a no-op on modern iwd, so don't let it lie to us. If we have no + * tracked adapter at all, fall back to "any device exists". */ + if (eina_hash_population(m->adapters) > 0) + { + Eina_Iterator *ait = eina_hash_iterator_data_new(m->adapters); + Iwd_Adapter *ap; + EINA_ITERATOR_FOREACH(ait, ap) + if (ap->powered) any_powered = EINA_TRUE; + eina_iterator_free(ait); + } + else + any_powered = (eina_hash_population(m->devices) > 0); + + Eina_Iterator *it = eina_hash_iterator_data_new(m->devices); + Iwd_Device *d; + EINA_ITERATOR_FOREACH(it, d) + { + if (!any_powered) continue; /* radio is down: ignore stale station */ + if (!d->has_station) continue; + if (d->scanning && s < IWD_STATE_SCANNING) s = IWD_STATE_SCANNING; + if (d->station_state == IWD_STATION_CONNECTING && s < IWD_STATE_CONNECTING) s = IWD_STATE_CONNECTING; + if (d->station_state == IWD_STATION_CONNECTED && s < IWD_STATE_CONNECTED) s = IWD_STATE_CONNECTED; + } + eina_iterator_free(it); + if (s == IWD_STATE_OFF && any_powered) s = IWD_STATE_IDLE; + m->state = s; +} + +/* ----- D-Bus callbacks ------------------------------------------------- */ + +static void +_on_iface_added(void *data, const char *path, const char *iface, Eldbus_Message_Iter *props) +{ + Iwd_Manager *m = data; + Eldbus_Connection *conn = iwd_dbus_conn(m->dbus); + + if (!strcmp(iface, IWD_IFACE_ADAPTER)) + { + Iwd_Adapter *a = eina_hash_find(m->adapters, path); + if (!a) + { + a = iwd_adapter_new(conn, path, m); + if (a) eina_hash_add(m->adapters, path, a); + } + if (a) iwd_adapter_apply_props(a, props); + } + else if (!strcmp(iface, IWD_IFACE_DEVICE)) + { + Iwd_Device *d = eina_hash_find(m->devices, path); + if (!d) + { + d = iwd_device_new(conn, path, m); + if (d) eina_hash_add(m->devices, path, d); + } + if (d) iwd_device_apply_device_props(d, props); + } + else if (!strcmp(iface, IWD_IFACE_STATION)) + { + Iwd_Device *d = eina_hash_find(m->devices, path); + if (!d) + { + d = iwd_device_new(conn, path, m); + if (d) eina_hash_add(m->devices, path, d); + } + if (d) + { + iwd_device_attach_station(d); + iwd_device_apply_station_props(d, props); + } + } + else if (!strcmp(iface, IWD_IFACE_NETWORK)) + { + Iwd_Network *n = eina_hash_find(m->networks, path); + if (!n) + { + n = iwd_network_new(conn, path, m); + if (n) eina_hash_add(m->networks, path, n); + } + if (n) iwd_network_apply_props(n, props); + } + /* Adapter / KnownNetwork: TODO (not needed for first connect path) */ + + iwd_manager_notify(m); +} + +static void +_on_iface_removed(void *data, const char *path, const char *iface) +{ + Iwd_Manager *m = data; + + if (!strcmp(iface, IWD_IFACE_STATION)) + { + Iwd_Device *d = eina_hash_find(m->devices, path); + if (d) iwd_device_detach_station(d); + } + else if (!strcmp(iface, IWD_IFACE_DEVICE)) + { + eina_hash_del(m->devices, path, NULL); + } + else if (!strcmp(iface, IWD_IFACE_NETWORK)) + { + eina_hash_del(m->networks, path, NULL); + } + else if (!strcmp(iface, IWD_IFACE_ADAPTER)) + { + eina_hash_del(m->adapters, path, NULL); + } + iwd_manager_notify(m); +} + +static void +_on_name_appeared(void *data EINA_UNUSED) { /* GetManagedObjects will populate */ } + +static void +_on_name_vanished(void *data) +{ + Iwd_Manager *m = data; + eina_hash_free_buckets(m->adapters); + eina_hash_free_buckets(m->devices); + eina_hash_free_buckets(m->networks); + m->state = IWD_STATE_OFF; + iwd_manager_notify(m); +} + +/* ----- lifecycle ------------------------------------------------------- */ + +static void _adapter_free_cb(void *d) { iwd_adapter_free(d); } +static void _device_free_cb (void *d) { iwd_device_free(d); } +static void _network_free_cb(void *d) { iwd_network_free(d); } + +Iwd_Manager * +iwd_manager_new(Eldbus_Connection *conn) +{ + Iwd_Manager *m = calloc(1, sizeof(*m)); + if (!m) return NULL; + m->adapters = eina_hash_string_superfast_new(_adapter_free_cb); + m->devices = eina_hash_string_superfast_new(_device_free_cb); + m->networks = eina_hash_string_superfast_new(_network_free_cb); + m->state = IWD_STATE_OFF; + + Iwd_Dbus_Callbacks cbs = { + .name_appeared = _on_name_appeared, + .name_vanished = _on_name_vanished, + .iface_added = _on_iface_added, + .iface_removed = _on_iface_removed, + }; + m->dbus = iwd_dbus_new(conn, &cbs, m); + m->agent = iwd_agent_new(conn, _passphrase_trampoline, m); + return m; +} + +void +iwd_manager_free(Iwd_Manager *m) +{ + if (!m) return; + iwd_agent_free(m->agent); + iwd_dbus_free(m->dbus); + eina_hash_free(m->adapters); + eina_hash_free(m->devices); + eina_hash_free(m->networks); + Listener *li; + EINA_LIST_FREE(m->listeners, li) free(li); + free(m); +} + +Iwd_State iwd_manager_state (const Iwd_Manager *m) { return m ? m->state : IWD_STATE_OFF; } +const Eina_Hash *iwd_manager_devices (const Iwd_Manager *m) { return m ? m->devices : NULL; } +const Eina_Hash *iwd_manager_networks (const Iwd_Manager *m) { return m ? m->networks : NULL; } + +void +iwd_manager_scan_request(Iwd_Manager *m) +{ + if (!m) return; + Eina_Iterator *it = eina_hash_iterator_data_new(m->devices); + Iwd_Device *d; + EINA_ITERATOR_FOREACH(it, d) iwd_device_scan(d); + eina_iterator_free(it); +} + +void +iwd_manager_set_powered(Iwd_Manager *m, Eina_Bool on) +{ + if (!m) return; + + if (!on) + { + Eina_Iterator *dit = eina_hash_iterator_data_new(m->devices); + Iwd_Device *d; + EINA_ITERATOR_FOREACH(dit, d) iwd_device_disconnect(d); + eina_iterator_free(dit); + } + + Eina_Iterator *it = eina_hash_iterator_data_new(m->adapters); + Iwd_Adapter *a; + EINA_ITERATOR_FOREACH(it, a) iwd_adapter_set_powered(a, on); + eina_iterator_free(it); +} diff --git a/src/iwd/iwd_manager.h b/src/iwd/iwd_manager.h new file mode 100644 index 0000000..900fad4 --- /dev/null +++ b/src/iwd/iwd_manager.h @@ -0,0 +1,50 @@ +#ifndef IWD_MANAGER_H +#define IWD_MANAGER_H + +#include +#include +#include "iwd_agent.h" + +typedef enum { + IWD_STATE_OFF, + IWD_STATE_IDLE, + IWD_STATE_SCANNING, + IWD_STATE_CONNECTING, + IWD_STATE_CONNECTED, + IWD_STATE_ERROR, +} Iwd_State; + +typedef struct _Iwd_Manager Iwd_Manager; + +Iwd_Manager *iwd_manager_new (Eldbus_Connection *conn); +void iwd_manager_free(Iwd_Manager *m); + +Iwd_State iwd_manager_state (const Iwd_Manager *m); + +/* Hash */ +const Eina_Hash *iwd_manager_devices (const Iwd_Manager *m); +/* Hash */ +const Eina_Hash *iwd_manager_networks(const Iwd_Manager *m); + +void iwd_manager_scan_request (Iwd_Manager *m); +void iwd_manager_set_powered (Iwd_Manager *m, Eina_Bool on); + +typedef void (*Iwd_Manager_Cb)(void *data, Iwd_Manager *m); +void iwd_manager_listener_add (Iwd_Manager *m, Iwd_Manager_Cb cb, void *data); +void iwd_manager_listener_del (Iwd_Manager *m, Iwd_Manager_Cb cb, void *data); + +/* Internal: invoked by sub-objects when their state changes. */ +void iwd_manager_notify (Iwd_Manager *m); + +/* The UI installs its passphrase prompt here. The handler must + * eventually call iwd_agent_reply()/iwd_agent_cancel() with the request. */ +void iwd_manager_set_passphrase_handler(Iwd_Manager *m, + Iwd_Agent_Passphrase_Cb cb, + void *data); + +/* Notified when iwd issues Agent.Cancel — UI should close any open prompt. */ +void iwd_manager_set_cancel_handler (Iwd_Manager *m, + Iwd_Agent_Cancel_Cb cb, + void *data); + +#endif diff --git a/src/iwd/iwd_network.c b/src/iwd/iwd_network.c index 24cdab0..18a84d5 100644 --- a/src/iwd/iwd_network.c +++ b/src/iwd/iwd_network.c @@ -1,288 +1,131 @@ #include "iwd_network.h" #include "iwd_dbus.h" -#include "../e_mod_main.h" +#include "iwd_props.h" +#include "iwd_manager.h" +#include +#include +#include -/* Global network list */ -Eina_List *iwd_networks = NULL; - -/* Forward declarations */ -static void _network_properties_changed_cb(void *data, const Eldbus_Message *msg); -static void _network_parse_properties(IWD_Network *net, Eldbus_Message_Iter *properties); - -/* Create new network */ -IWD_Network * -iwd_network_new(const char *path) +static Iwd_Security +_sec_from_str(const char *s) { - IWD_Network *net; - Eldbus_Connection *conn; - Eldbus_Object *obj; - - if (!path) return NULL; - - conn = iwd_dbus_conn_get(); - if (!conn) return NULL; - - /* Check if network already exists */ - net = iwd_network_find(path); - if (net) - { - DBG("Network already exists: %s", path); - return net; - } - - DBG("Creating new network: %s", path); - - net = E_NEW(IWD_Network, 1); - if (!net) return NULL; - - net->path = eina_stringshare_add(path); - - /* Create D-Bus object */ - obj = eldbus_object_get(conn, IWD_SERVICE, path); - if (!obj) - { - ERR("Failed to get D-Bus object for network"); - eina_stringshare_del(net->path); - E_FREE(net); - return NULL; - } - - /* Get proxy */ - net->network_proxy = eldbus_proxy_get(obj, IWD_NETWORK_INTERFACE); - - /* Subscribe to property changes */ - net->properties_changed = - eldbus_proxy_signal_handler_add(net->network_proxy, - "PropertiesChanged", - _network_properties_changed_cb, - net); - - /* Add to global list */ - iwd_networks = eina_list_append(iwd_networks, net); - - INF("Created network: %s", path); - - eldbus_object_unref(obj); - return net; + if (!s) return IWD_SEC_UNKNOWN; + if (!strcmp(s, "open")) return IWD_SEC_OPEN; + if (!strcmp(s, "psk")) return IWD_SEC_PSK; + if (!strcmp(s, "8021x")) return IWD_SEC_8021X; + if (!strcmp(s, "wep")) return IWD_SEC_WEP; + return IWD_SEC_UNKNOWN; } -/* Free network */ -void -iwd_network_free(IWD_Network *net) -{ - if (!net) return; - - DBG("Freeing network: %s", net->path); - - iwd_networks = eina_list_remove(iwd_networks, net); - - if (net->properties_changed) - eldbus_signal_handler_del(net->properties_changed); - - eina_stringshare_del(net->path); - eina_stringshare_del(net->name); - eina_stringshare_del(net->type); - eina_stringshare_del(net->last_connected_time); - - E_FREE(net); -} - -/* Find network by path */ -IWD_Network * -iwd_network_find(const char *path) -{ - Eina_List *l; - IWD_Network *net; - - if (!path) return NULL; - - EINA_LIST_FOREACH(iwd_networks, l, net) - { - if (net->path && strcmp(net->path, path) == 0) - return net; - } - - return NULL; -} - -/* Connect error callback */ static void -_network_connect_error_cb(void *data EINA_UNUSED, - const Eldbus_Message *msg, - Eldbus_Pending *pending EINA_UNUSED) +_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v) { - const char *err_name, *err_msg; - - if (eldbus_message_error_get(msg, &err_name, &err_msg)) - { - ERR("Failed to connect: %s: %s", err_name, err_msg); - - /* Show user-friendly error */ - if (strstr(err_name, "NotAuthorized") || strstr(err_msg, "Not authorized")) - { - e_util_dialog_show("Permission Denied", - "You do not have permission to manage Wi-Fi.
" - "Please configure polkit rules for iwd."); - } - else if (strstr(err_name, "Failed") || strstr(err_msg, "operation failed")) - { - e_util_dialog_show("Connection Failed", - "Failed to connect to the network.
" - "Please check your password and try again."); - } - else - { - char buf[512]; - snprintf(buf, sizeof(buf), "Connection error:
%s", err_msg ? err_msg : err_name); - e_util_dialog_show("Connection Error", buf); - } - } + Iwd_Network *n = data; + if (!strcmp(key, "Name")) { free(n->ssid); n->ssid = iwd_props_str_dup(v); } + else if (!strcmp(key, "Type")) { char *s = iwd_props_str_dup(v); n->security = _sec_from_str(s); free(s); } + else if (!strcmp(key, "Connected")) { n->connected = iwd_props_bool(v); } + else if (!strcmp(key, "Device")) { free(n->device_path); n->device_path = iwd_props_str_dup(v); } + else if (!strcmp(key, "KnownNetwork")) { free(n->known_path); n->known_path = iwd_props_str_dup(v); } } -/* Connect to network */ void -iwd_network_connect(IWD_Network *net) +iwd_network_apply_props(Iwd_Network *n, Eldbus_Message_Iter *props) { - if (!net || !net->network_proxy) - { - ERR("Invalid network for connect"); - return; - } - - DBG("Connecting to network: %s", net->name ? net->name : net->path); - - /* This will trigger agent RequestPassphrase if needed */ - eldbus_proxy_call(net->network_proxy, "Connect", - _network_connect_error_cb, NULL, -1, ""); + iwd_props_for_each(props, _prop_cb, n); } -/* Forget network */ -void -iwd_network_forget(IWD_Network *net) -{ - Eldbus_Proxy *known_proxy; - - if (!net || !net->network_proxy || !net->known) - { - ERR("Invalid network for forget or network not known"); - return; - } - - DBG("Forgetting network: %s", net->name ? net->name : net->path); - - /* Get KnownNetwork proxy (same path, different interface) */ - known_proxy = eldbus_proxy_get(eldbus_proxy_object_get(net->network_proxy), - IWD_KNOWN_NETWORK_INTERFACE); - if (!known_proxy) - { - ERR("Failed to get KnownNetwork proxy"); - return; - } - - eldbus_proxy_call(known_proxy, "Forget", NULL, NULL, -1, ""); -} - -/* Get networks list */ -Eina_List * -iwd_networks_get(void) -{ - return iwd_networks; -} - -/* Initialize network subsystem */ -void -iwd_network_init(void) -{ - DBG("Initializing network subsystem"); - /* Networks will be populated from ObjectManager signals */ -} - -/* Shutdown network subsystem */ -void -iwd_network_shutdown(void) -{ - IWD_Network *net; - - DBG("Shutting down network subsystem"); - - EINA_LIST_FREE(iwd_networks, net) - iwd_network_free(net); -} - -/* Properties changed callback */ static void -_network_properties_changed_cb(void *data, - const Eldbus_Message *msg) +_on_props_changed(void *data, const Eldbus_Message *msg) { - IWD_Network *net = data; - const char *interface; + Iwd_Network *n = data; + const char *iface; Eldbus_Message_Iter *changed, *invalidated; - - if (!eldbus_message_arguments_get(msg, "sa{sv}as", &interface, &changed, &invalidated)) - { - ERR("Failed to parse PropertiesChanged signal"); - return; - } - - DBG("Properties changed for network %s on interface %s", net->path, interface); - - _network_parse_properties(net, changed); - - /* TODO: Notify UI of changes */ + if (!eldbus_message_arguments_get(msg, "sa{sv}as", &iface, &changed, &invalidated)) + return; + if (strcmp(iface, IWD_IFACE_NETWORK) != 0) return; + iwd_props_for_each(changed, _prop_cb, n); + if (n->manager) iwd_manager_notify(n->manager); } -/* Parse network properties */ -static void -_network_parse_properties(IWD_Network *net, - Eldbus_Message_Iter *properties) +Iwd_Network * +iwd_network_new(Eldbus_Connection *conn, const char *path, void *manager) { - Eldbus_Message_Iter *entry; - - if (!properties) return; - - while (eldbus_message_iter_get_and_next(properties, 'e', &entry)) - { - const char *key; - Eldbus_Message_Iter *var; - - if (!eldbus_message_iter_arguments_get(entry, "sv", &key, &var)) - continue; - - if (strcmp(key, "Name") == 0) - { - const char *name; - if (eldbus_message_iter_arguments_get(var, "s", &name)) - { - eina_stringshare_replace(&net->name, name); - DBG(" Name: %s", net->name); - } - } - else if (strcmp(key, "Type") == 0) - { - const char *type; - if (eldbus_message_iter_arguments_get(var, "s", &type)) - { - eina_stringshare_replace(&net->type, type); - DBG(" Type: %s", net->type); - } - } - else if (strcmp(key, "Known") == 0) - { - Eina_Bool known; - if (eldbus_message_iter_arguments_get(var, "b", &known)) - { - net->known = known; - DBG(" Known: %d", net->known); - } - } - else if (strcmp(key, "AutoConnect") == 0) - { - Eina_Bool auto_connect; - if (eldbus_message_iter_arguments_get(var, "b", &auto_connect)) - { - net->auto_connect = auto_connect; - DBG(" Auto-connect: %d", net->auto_connect); - } - } - } + Iwd_Network *n = calloc(1, sizeof(*n)); + if (!n) return NULL; + n->path = path ? strdup(path) : NULL; + n->manager = manager; + n->security = IWD_SEC_UNKNOWN; + n->obj = eldbus_object_get(conn, IWD_BUS_NAME, path); + if (n->obj) + { + n->proxy = eldbus_proxy_get(n->obj, IWD_IFACE_NETWORK); + if (n->proxy) + n->sh_props = eldbus_proxy_properties_changed_callback_add( + n->proxy, _on_props_changed, n); + } + return n; +} + +void +iwd_network_free(Iwd_Network *n) +{ + if (!n) return; + if (n->sh_props) eldbus_signal_handler_del(n->sh_props); + if (n->proxy) eldbus_proxy_unref(n->proxy); + if (n->obj) eldbus_object_unref(n->obj); + free(n->path); + free(n->ssid); + free(n->device_path); + free(n->known_path); + free(n); +} + +static void +_on_connect_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) +{ + const char *en, *em; + const char *ssid = data; + if (eldbus_message_error_get(msg, &en, &em)) + fprintf(stderr, "e_iwd: connect to '%s' failed: %s: %s\n", + ssid ? ssid : "?", en, em); + free(data); +} + +int +iwd_network_signal_tier(const Iwd_Network *n) +{ + if (!n || !n->have_signal) return 0; + /* iwd reports signal in 100*dBm. Cutoffs in dBm: -60/-67/-74/-80. */ + int dbm = n->signal_dbm / 100; + if (dbm >= -60) return 4; + if (dbm >= -67) return 3; + if (dbm >= -74) return 2; + if (dbm >= -80) return 1; + return 1; +} + +void +iwd_network_connect(Iwd_Network *n) +{ + if (!n || !n->proxy) return; + /* Network.Connect() takes no args; iwd will dial the registered Agent + * for a passphrase if needed. */ + eldbus_proxy_call(n->proxy, "Connect", _on_connect_reply, + n->ssid ? strdup(n->ssid) : NULL, -1, ""); +} + +void +iwd_network_forget(Iwd_Network *n) +{ + if (!n || !n->known_path || !n->obj) return; + Eldbus_Connection *conn = eldbus_object_connection_get(n->obj); + Eldbus_Object *kobj = eldbus_object_get(conn, IWD_BUS_NAME, n->known_path); + if (!kobj) return; + Eldbus_Proxy *kp = eldbus_proxy_get(kobj, IWD_IFACE_KNOWN_NETWORK); + if (kp) + { + eldbus_proxy_call(kp, "Forget", NULL, NULL, -1, ""); + eldbus_proxy_unref(kp); + } + eldbus_object_unref(kobj); } diff --git a/src/iwd/iwd_network.h b/src/iwd/iwd_network.h index 529aaca..dd7d56f 100644 --- a/src/iwd/iwd_network.h +++ b/src/iwd/iwd_network.h @@ -1,44 +1,50 @@ #ifndef IWD_NETWORK_H #define IWD_NETWORK_H +#include #include #include -/* Network structure */ -typedef struct _IWD_Network +typedef enum { + IWD_SEC_OPEN, + IWD_SEC_PSK, + IWD_SEC_8021X, + IWD_SEC_WEP, + IWD_SEC_UNKNOWN, +} Iwd_Security; + +typedef struct _Iwd_Network Iwd_Network; + +struct _Iwd_Network { - const char *path; /* D-Bus object path */ - const char *name; /* SSID (decoded) */ - const char *type; /* "open", "psk", "8021x" */ - Eina_Bool known; /* Is this a known network? */ - int16_t signal_strength; /* Signal strength in dBm */ + char *path; + char *ssid; + char *device_path; + char *known_path; + Iwd_Security security; + Eina_Bool connected; - /* Known network properties */ - Eina_Bool auto_connect; - const char *last_connected_time; + /* Signal strength in 100*dBm units (iwd convention). Valid iff have_signal. */ + int16_t signal_dbm; + Eina_Bool have_signal; - /* D-Bus objects */ - Eldbus_Proxy *network_proxy; - Eldbus_Signal_Handler *properties_changed; -} IWD_Network; + Eldbus_Object *obj; + Eldbus_Proxy *proxy; + Eldbus_Signal_Handler *sh_props; -/* Global network list */ -extern Eina_List *iwd_networks; + void *manager; +}; -/* Network management */ -IWD_Network *iwd_network_new(const char *path); -void iwd_network_free(IWD_Network *net); -IWD_Network *iwd_network_find(const char *path); +Iwd_Network *iwd_network_new (Eldbus_Connection *conn, const char *path, void *manager); +void iwd_network_free(Iwd_Network *n); -/* Network operations */ -void iwd_network_connect(IWD_Network *net); -void iwd_network_forget(IWD_Network *net); +void iwd_network_apply_props(Iwd_Network *n, Eldbus_Message_Iter *props); -/* Get networks list */ -Eina_List *iwd_networks_get(void); +/* 0 = unknown/no signal, 1..4 = weak..excellent. */ +int iwd_network_signal_tier(const Iwd_Network *n); -/* Initialize network subsystem */ -void iwd_network_init(void); -void iwd_network_shutdown(void); +void iwd_network_connect (Iwd_Network *n); +/* Forget acts on the KnownNetwork object referenced by this network. */ +void iwd_network_forget (Iwd_Network *n); #endif diff --git a/src/iwd/iwd_props.c b/src/iwd/iwd_props.c new file mode 100644 index 0000000..2f14696 --- /dev/null +++ b/src/iwd/iwd_props.c @@ -0,0 +1,38 @@ +#include "iwd_props.h" +#include + +void +iwd_props_for_each(Eldbus_Message_Iter *dict, Iwd_Prop_Cb cb, void *data) +{ + if (!dict || !cb) return; + Eldbus_Message_Iter *entry; + while (eldbus_message_iter_get_and_next(dict, 'e', &entry)) + { + const char *key; + Eldbus_Message_Iter *variant; + if (!eldbus_message_iter_arguments_get(entry, "sv", &key, &variant)) + continue; + cb(data, key, variant); + } +} + +char * +iwd_props_str_dup(Eldbus_Message_Iter *variant) +{ + if (!variant) return NULL; + const char *sig = eldbus_message_iter_signature_get(variant); + if (!sig || (sig[0] != 's' && sig[0] != 'o')) { free((void *)sig); return NULL; } + const char *s = NULL; + char want[2] = { sig[0], 0 }; + free((void *)sig); + if (!eldbus_message_iter_arguments_get(variant, want, &s)) return NULL; + return s ? strdup(s) : NULL; +} + +Eina_Bool +iwd_props_bool(Eldbus_Message_Iter *variant) +{ + Eina_Bool b = EINA_FALSE; + if (variant) eldbus_message_iter_arguments_get(variant, "b", &b); + return b; +} diff --git a/src/iwd/iwd_props.h b/src/iwd/iwd_props.h new file mode 100644 index 0000000..1ae3754 --- /dev/null +++ b/src/iwd/iwd_props.h @@ -0,0 +1,15 @@ +#ifndef IWD_PROPS_H +#define IWD_PROPS_H + +#include +#include + +/* Iterate an a{sv} message iter, calling cb for every entry. */ +typedef void (*Iwd_Prop_Cb)(void *data, const char *key, Eldbus_Message_Iter *value); +void iwd_props_for_each(Eldbus_Message_Iter *dict, Iwd_Prop_Cb cb, void *data); + +/* Extract a string from a variant iter ("s" or "o"). Returns strdup'd copy. */ +char *iwd_props_str_dup(Eldbus_Message_Iter *variant); +Eina_Bool iwd_props_bool(Eldbus_Message_Iter *variant); + +#endif diff --git a/src/iwd/iwd_state.c b/src/iwd/iwd_state.c deleted file mode 100644 index faeb43a..0000000 --- a/src/iwd/iwd_state.c +++ /dev/null @@ -1,153 +0,0 @@ -#include "iwd_state.h" -#include "../e_mod_main.h" - -/* Global state */ -static IWD_State current_state = IWD_STATE_OFF; -static Eina_List *state_change_callbacks = NULL; - -/* State change callback structure */ -typedef struct _State_Callback -{ - IWD_State_Changed_Cb cb; - void *data; -} State_Callback; - -/* Initialize state subsystem */ -void -iwd_state_init(void) -{ - DBG("Initializing state subsystem"); - current_state = IWD_STATE_OFF; -} - -/* Shutdown state subsystem */ -void -iwd_state_shutdown(void) -{ - State_Callback *scb; - - DBG("Shutting down state subsystem"); - - EINA_LIST_FREE(state_change_callbacks, scb) - E_FREE(scb); -} - -/* Get current state */ -IWD_State -iwd_state_get(void) -{ - return current_state; -} - -/* Set state and notify callbacks */ -void -iwd_state_set(IWD_State state) -{ - IWD_State old_state; - Eina_List *l; - State_Callback *scb; - - if (current_state == state) return; - - old_state = current_state; - current_state = state; - - DBG("State changed: %d -> %d", old_state, state); - - /* Notify callbacks */ - EINA_LIST_FOREACH(state_change_callbacks, l, scb) - { - if (scb->cb) - scb->cb(scb->data, old_state, state); - } -} - -/* Add state change callback */ -void -iwd_state_callback_add(IWD_State_Changed_Cb cb, void *data) -{ - State_Callback *scb; - - if (!cb) return; - - scb = E_NEW(State_Callback, 1); - if (!scb) return; - - scb->cb = cb; - scb->data = data; - - state_change_callbacks = eina_list_append(state_change_callbacks, scb); -} - -/* Remove state change callback */ -void -iwd_state_callback_del(IWD_State_Changed_Cb cb, void *data) -{ - Eina_List *l, *l_next; - State_Callback *scb; - - EINA_LIST_FOREACH_SAFE(state_change_callbacks, l, l_next, scb) - { - if (scb->cb == cb && scb->data == data) - { - state_change_callbacks = eina_list_remove_list(state_change_callbacks, l); - E_FREE(scb); - return; - } - } -} - -/* Update state from device */ -void -iwd_state_update_from_device(IWD_Device *dev) -{ - if (!dev) - { - iwd_state_set(IWD_STATE_ERROR); - return; - } - - if (!dev->powered) - { - iwd_state_set(IWD_STATE_OFF); - return; - } - - if (dev->scanning) - { - iwd_state_set(IWD_STATE_SCANNING); - return; - } - - if (dev->state) - { - if (strcmp(dev->state, "connected") == 0) - iwd_state_set(IWD_STATE_CONNECTED); - else if (strcmp(dev->state, "connecting") == 0) - iwd_state_set(IWD_STATE_CONNECTING); - else if (strcmp(dev->state, "disconnecting") == 0) - iwd_state_set(IWD_STATE_IDLE); - else - iwd_state_set(IWD_STATE_IDLE); - } - else - { - iwd_state_set(IWD_STATE_IDLE); - } -} - -/* Get state name */ -const char * -iwd_state_name_get(IWD_State state) -{ - switch (state) - { - case IWD_STATE_OFF: return "OFF"; - case IWD_STATE_IDLE: return "IDLE"; - case IWD_STATE_SCANNING: return "SCANNING"; - case IWD_STATE_CONNECTING: return "CONNECTING"; - case IWD_STATE_CONNECTED: return "CONNECTED"; - case IWD_STATE_ERROR: return "ERROR"; - default: return "UNKNOWN"; - } -} diff --git a/src/iwd/iwd_state.h b/src/iwd/iwd_state.h deleted file mode 100644 index 67ccadc..0000000 --- a/src/iwd/iwd_state.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef IWD_STATE_H -#define IWD_STATE_H - -#include - -/* Forward declaration */ -typedef struct _IWD_Device IWD_Device; - -/* Connection states */ -typedef enum -{ - IWD_STATE_OFF, /* Powered = false */ - IWD_STATE_IDLE, /* Powered = true, disconnected */ - IWD_STATE_SCANNING, /* Scanning in progress */ - IWD_STATE_CONNECTING, /* Connecting to network */ - IWD_STATE_CONNECTED, /* Connected to network */ - IWD_STATE_ERROR /* iwd not running or error */ -} IWD_State; - -/* State change callback */ -typedef void (*IWD_State_Changed_Cb)(void *data, IWD_State old_state, IWD_State new_state); - -/* Initialize/shutdown */ -void iwd_state_init(void); -void iwd_state_shutdown(void); - -/* Get/set state */ -IWD_State iwd_state_get(void); -void iwd_state_set(IWD_State state); - -/* State callbacks */ -void iwd_state_callback_add(IWD_State_Changed_Cb cb, void *data); -void iwd_state_callback_del(IWD_State_Changed_Cb cb, void *data); - -/* Update state from device */ -void iwd_state_update_from_device(IWD_Device *dev); - -/* Get state name string */ -const char *iwd_state_name_get(IWD_State state); - -#endif diff --git a/src/meson.build b/src/meson.build index cd275ec..a7a4324 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,36 +1,27 @@ -module_sources = files( +e_iwd_sources = [ 'e_mod_main.c', 'e_mod_config.c', 'e_mod_gadget.c', 'e_mod_popup.c', 'iwd/iwd_dbus.c', + 'iwd/iwd_adapter.c', + 'iwd/iwd_props.c', + 'iwd/iwd_agent.c', + 'iwd/iwd_manager.c', 'iwd/iwd_device.c', 'iwd/iwd_network.c', - 'iwd/iwd_agent.c', - 'iwd/iwd_state.c', + 'ui/wifi_list.c', 'ui/wifi_auth.c', 'ui/wifi_hidden.c', -) - -# All core functionality implemented - -module_deps = [ - enlightenment, - eldbus, - elementary, - ecore, - evas, - edje, - eina + 'ui/wifi_status.c', ] -module_includes = include_directories('.', 'iwd', 'ui') - shared_module('module', - module_sources, - dependencies: module_deps, - include_directories: module_includes, - name_prefix: '', - install: true, - install_dir: dir_module_arch + e_iwd_sources, + name_prefix : '', + name_suffix : 'so', + dependencies : [eldbus, elementary, enlightenment], + include_directories : include_directories('.', 'iwd', 'ui'), + install : true, + install_dir : join_paths(module_dir, module_arch), ) diff --git a/src/ui/wifi_auth.c b/src/ui/wifi_auth.c index ab72f25..8fdb1e5 100644 --- a/src/ui/wifi_auth.c +++ b/src/ui/wifi_auth.c @@ -1,171 +1,115 @@ -#include "../e_mod_main.h" +#include "wifi_auth.h" +#include -/* Auth dialog structure */ -typedef struct _Auth_Dialog +typedef struct _Auth_Ctx { - Instance *inst; - IWD_Network *network; - E_Dialog *dialog; + Evas_Object *win; /* top-level window hosting the popup */ + Evas_Object *popup; Evas_Object *entry; - char *passphrase; -} Auth_Dialog; + Wifi_Auth_Cb cb; + void *data; + Eina_Bool fired; +} Auth_Ctx; -/* Global auth dialog (only one at a time) */ -static Auth_Dialog *auth_dialog = NULL; - -/* Forward declarations */ -static void _auth_dialog_ok_cb(void *data, E_Dialog *dialog); -static void _auth_dialog_cancel_cb(void *data, E_Dialog *dialog); -static void _auth_dialog_free(Auth_Dialog *ad); - -/* Show authentication dialog */ -void -wifi_auth_dialog_show(Instance *inst, IWD_Network *net) -{ - Auth_Dialog *ad; - E_Dialog *dia; - Evas_Object *o, *entry; - char buf[512]; - - if (!inst || !net) return; - - /* Only one auth dialog at a time */ - if (auth_dialog) - { - WRN("Auth dialog already open"); - return; - } - - DBG("Showing auth dialog for network: %s", net->name ? net->name : net->path); - - ad = E_NEW(Auth_Dialog, 1); - if (!ad) return; - - ad->inst = inst; - ad->network = net; - auth_dialog = ad; - - /* Create dialog */ - dia = e_dialog_new(NULL, "E", "iwd_passphrase"); - if (!dia) - { - _auth_dialog_free(ad); - return; - } - - ad->dialog = dia; - - e_dialog_title_set(dia, "Wi-Fi Authentication"); - e_dialog_icon_set(dia, "network-wireless", 48); - - /* Message */ - snprintf(buf, sizeof(buf), - "Enter passphrase for network:
" - "%s

" - "Security: %s", - net->name ? net->name : "Unknown", - net->type ? (strcmp(net->type, "psk") == 0 ? "WPA2/WPA3" : net->type) : "Unknown"); - - o = e_widget_label_add(evas_object_evas_get(dia->win), buf); - e_widget_size_min_set(o, 300, 40); - - /* Entry for passphrase */ - entry = e_widget_entry_add(evas_object_evas_get(dia->win), &ad->passphrase, NULL, NULL, NULL); - e_widget_entry_password_set(entry, 1); - e_widget_size_min_set(entry, 280, 30); - - /* Pack into a list */ - Evas_Object *list = e_widget_list_add(evas_object_evas_get(dia->win), 0, 0); - e_widget_list_object_append(list, o, 1, 1, 0.5); - e_widget_list_object_append(list, entry, 1, 1, 0.5); - - e_dialog_content_set(dia, list, 300, 120); - ad->entry = entry; - - /* Buttons */ - e_dialog_button_add(dia, "Connect", NULL, _auth_dialog_ok_cb, ad); - e_dialog_button_add(dia, "Cancel", NULL, _auth_dialog_cancel_cb, ad); - - e_dialog_button_focus_num(dia, 0); - e_dialog_show(dia); - - INF("Auth dialog shown"); -} - -/* OK button callback */ static void -_auth_dialog_ok_cb(void *data, E_Dialog *dialog EINA_UNUSED) +_finish(Auth_Ctx *c, Eina_Bool ok, const char *pass) { - Auth_Dialog *ad = data; - - if (!ad) return; - - DBG("Auth dialog OK clicked"); - - if (!ad->passphrase || strlen(ad->passphrase) < 8) - { - e_util_dialog_show("Error", - "Passphrase must be at least 8 characters long."); - return; - } - - /* Store passphrase in agent */ - iwd_agent_set_passphrase(ad->passphrase); - - /* Initiate connection */ - if (ad->network) - { - INF("Connecting to network: %s", ad->network->name); - iwd_network_connect(ad->network); - } - - /* Close dialog */ - _auth_dialog_free(ad); + if (c->fired) return; + c->fired = EINA_TRUE; + if (c->cb) c->cb(c->data, pass, ok); + if (c->win) evas_object_del(c->win); + free(c); } -/* Cancel button callback */ static void -_auth_dialog_cancel_cb(void *data, E_Dialog *dialog EINA_UNUSED) +_on_ok(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED) { - Auth_Dialog *ad = data; - - DBG("Auth dialog cancelled"); - - /* Cancel agent request */ - iwd_agent_cancel(); - - _auth_dialog_free(ad); + Auth_Ctx *c = data; + _finish(c, EINA_TRUE, elm_entry_entry_get(c->entry)); } -/* Free auth dialog */ static void -_auth_dialog_free(Auth_Dialog *ad) +_on_cancel(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED) { - if (!ad) return; - - DBG("Freeing auth dialog"); - - if (ad->dialog) - e_object_del(E_OBJECT(ad->dialog)); - - /* Clear passphrase from memory */ - if (ad->passphrase) - { - memset(ad->passphrase, 0, strlen(ad->passphrase)); - E_FREE(ad->passphrase); - } - - E_FREE(ad); - auth_dialog = NULL; + _finish(data, EINA_FALSE, NULL); } -/* Cancel any open auth dialog */ -void -wifi_auth_dialog_cancel(void) +static void +_on_del(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED) { - if (auth_dialog) - { - DBG("Cancelling auth dialog from external request"); - _auth_dialog_free(auth_dialog); - } + /* Window closed without cancel/ok — treat as cancel. */ + _finish(data, EINA_FALSE, NULL); +} + +Evas_Object * +wifi_auth_prompt(Evas_Object *parent EINA_UNUSED, const char *ssid, + const char *security, + Wifi_Auth_Cb cb, void *data) +{ + Auth_Ctx *c = calloc(1, sizeof(*c)); + c->cb = cb; c->data = data; + + /* A floating top-level window so the popup is actually visible — + * elm_popup parented to a gadcon popup's sub-canvas never shows. */ + Evas_Object *win = elm_win_add(NULL, "eiwd-auth", ELM_WIN_DIALOG_BASIC); + elm_win_title_set(win, "iwd Wi-Fi"); + elm_win_autodel_set(win, EINA_TRUE); + elm_win_center(win, EINA_TRUE, EINA_TRUE); + evas_object_resize(win, 360, 200); + c->win = win; + + Evas_Object *bg = elm_bg_add(win); + evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_win_resize_object_add(win, bg); + evas_object_show(bg); + + Evas_Object *p = elm_popup_add(win); + c->popup = p; + char title[256]; + snprintf(title, sizeof(title), "Connect to %s", ssid ? ssid : "network"); + elm_object_part_text_set(p, "title,text", title); + + Evas_Object *box = elm_box_add(p); + elm_box_padding_set(box, 0, 6); + + if (security && *security) + { + char buf[128]; + snprintf(buf, sizeof(buf), "Security: %s", security); + Evas_Object *lbl = elm_label_add(box); + elm_object_text_set(lbl, buf); + evas_object_size_hint_align_set(lbl, 0.0, 0.5); + elm_box_pack_end(box, lbl); + evas_object_show(lbl); + } + + Evas_Object *entry = elm_entry_add(box); + elm_entry_single_line_set(entry, EINA_TRUE); + elm_entry_password_set(entry, EINA_TRUE); + elm_entry_scrollable_set(entry, EINA_TRUE); + evas_object_size_hint_weight_set(entry, EVAS_HINT_EXPAND, 0); + evas_object_size_hint_align_set(entry, EVAS_HINT_FILL, 0); + elm_box_pack_end(box, entry); + evas_object_show(entry); + c->entry = entry; + + evas_object_show(box); + elm_object_content_set(p, box); + + Evas_Object *bcancel = elm_button_add(p); + elm_object_text_set(bcancel, "Cancel"); + elm_object_part_content_set(p, "button1", bcancel); + evas_object_smart_callback_add(bcancel, "clicked", _on_cancel, c); + + Evas_Object *bok = elm_button_add(p); + elm_object_text_set(bok, "Connect"); + elm_object_part_content_set(p, "button2", bok); + evas_object_smart_callback_add(bok, "clicked", _on_ok, c); + + evas_object_event_callback_add(win, EVAS_CALLBACK_DEL, _on_del, c); + + evas_object_show(p); + evas_object_show(win); + elm_object_focus_set(entry, EINA_TRUE); + return win; } diff --git a/src/ui/wifi_auth.h b/src/ui/wifi_auth.h index 07b3121..79c6ee6 100644 --- a/src/ui/wifi_auth.h +++ b/src/ui/wifi_auth.h @@ -1,16 +1,19 @@ #ifndef WIFI_AUTH_H #define WIFI_AUTH_H -#include +#include -/* Forward declarations */ -typedef struct _Instance Instance; -typedef struct _IWD_Network IWD_Network; +typedef void (*Wifi_Auth_Cb)(void *data, const char *passphrase, Eina_Bool ok); -/* Show authentication dialog for a network */ -void wifi_auth_dialog_show(Instance *inst, IWD_Network *net); - -/* Cancel/close authentication dialog */ -void wifi_auth_dialog_cancel(void); +/* Show a modal passphrase dialog. security is an optional human label + * (e.g. "WPA", "WEP") shown alongside the SSID; pass NULL to omit it. + * cb is called exactly once with ok=EINA_TRUE + passphrase, or + * ok=EINA_FALSE on cancel. The dialog destroys itself. */ +/* Returns the popup widget so the caller can dismiss it externally + * (e.g. on Agent.Cancel from iwd). The widget self-deletes on user + * action; treat the returned pointer as a weak reference. */ +Evas_Object *wifi_auth_prompt(Evas_Object *parent, const char *ssid, + const char *security, + Wifi_Auth_Cb cb, void *data); #endif diff --git a/src/ui/wifi_hidden.c b/src/ui/wifi_hidden.c index f08df75..1e936f8 100644 --- a/src/ui/wifi_hidden.c +++ b/src/ui/wifi_hidden.c @@ -1,190 +1,115 @@ -#include "../e_mod_main.h" +#include "wifi_hidden.h" +#include -/* Hidden network dialog structure */ -typedef struct _Hidden_Dialog +typedef struct _Hidden_Ctx { - Instance *inst; - E_Dialog *dialog; - Evas_Object *ssid_entry; - Evas_Object *pass_entry; - char *ssid; - char *passphrase; - Eina_Bool has_password; -} Hidden_Dialog; + Evas_Object *win; + Evas_Object *popup; + Evas_Object *e_ssid; + Evas_Object *e_pass; + Wifi_Hidden_Cb cb; + void *data; + Eina_Bool fired; +} Hidden_Ctx; -/* Global hidden dialog */ -static Hidden_Dialog *hidden_dialog = NULL; +static void +_finish(Hidden_Ctx *c, Eina_Bool ok) +{ + if (c->fired) return; + c->fired = EINA_TRUE; + const char *ssid = ok ? elm_entry_entry_get(c->e_ssid) : NULL; + const char *pass = ok ? elm_entry_entry_get(c->e_pass) : NULL; + if (c->cb) c->cb(c->data, ssid, pass, ok); + if (c->win) evas_object_del(c->win); + free(c); +} -/* Forward declarations */ -static void _hidden_dialog_ok_cb(void *data, E_Dialog *dialog); -static void _hidden_dialog_cancel_cb(void *data, E_Dialog *dialog); -static void _hidden_dialog_free(Hidden_Dialog *hd); +static void +_on_ok(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED) +{ + Hidden_Ctx *c = data; + const char *ssid = elm_entry_entry_get(c->e_ssid); + if (!ssid || !*ssid) return; /* require non-empty SSID */ + _finish(c, EINA_TRUE); +} + +static void +_on_cancel(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED) +{ + _finish(data, EINA_FALSE); +} + +static void +_on_del(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED) +{ + _finish(data, EINA_FALSE); +} + +static Evas_Object * +_labelled_entry(Evas_Object *box, const char *label_text, Eina_Bool password) +{ + Evas_Object *lbl = elm_label_add(box); + elm_object_text_set(lbl, label_text); + evas_object_size_hint_align_set(lbl, 0.0, 0.5); + elm_box_pack_end(box, lbl); + evas_object_show(lbl); + + Evas_Object *e = elm_entry_add(box); + elm_entry_single_line_set(e, EINA_TRUE); + elm_entry_scrollable_set(e, EINA_TRUE); + if (password) elm_entry_password_set(e, EINA_TRUE); + evas_object_size_hint_weight_set(e, EVAS_HINT_EXPAND, 0); + evas_object_size_hint_align_set(e, EVAS_HINT_FILL, 0); + elm_box_pack_end(box, e); + evas_object_show(e); + return e; +} -/* Show hidden network dialog */ void -wifi_hidden_dialog_show(Instance *inst) +wifi_hidden_prompt(Evas_Object *parent EINA_UNUSED, Wifi_Hidden_Cb cb, void *data) { - Hidden_Dialog *hd; - E_Dialog *dia; - Evas_Object *o, *list, *ssid_entry, *pass_entry; + Hidden_Ctx *c = calloc(1, sizeof(*c)); + c->cb = cb; c->data = data; - if (!inst) return; + /* Floating top-level so the popup actually shows. */ + Evas_Object *win = elm_win_add(NULL, "eiwd-hidden", ELM_WIN_DIALOG_BASIC); + elm_win_title_set(win, "iwd Wi-Fi"); + elm_win_autodel_set(win, EINA_TRUE); + elm_win_center(win, EINA_TRUE, EINA_TRUE); + evas_object_resize(win, 360, 220); + c->win = win; - /* Only one hidden dialog at a time */ - if (hidden_dialog) - { - WRN("Hidden network dialog already open"); - return; - } + Evas_Object *bg = elm_bg_add(win); + evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_win_resize_object_add(win, bg); + evas_object_show(bg); - DBG("Showing hidden network dialog"); + Evas_Object *p = elm_popup_add(win); + c->popup = p; + elm_object_part_text_set(p, "title,text", "Connect to hidden network"); - hd = E_NEW(Hidden_Dialog, 1); - if (!hd) return; + Evas_Object *box = elm_box_add(p); + elm_box_padding_set(box, 0, 4); - hd->inst = inst; - hidden_dialog = hd; + c->e_ssid = _labelled_entry(box, "SSID:", EINA_FALSE); + c->e_pass = _labelled_entry(box, "Passphrase (optional):", EINA_TRUE); - /* Create dialog */ - dia = e_dialog_new(NULL, "E", "iwd_hidden_network"); - if (!dia) - { - _hidden_dialog_free(hd); - return; - } + evas_object_show(box); + elm_object_content_set(p, box); - hd->dialog = dia; + Evas_Object *bcancel = elm_button_add(p); + elm_object_text_set(bcancel, "Cancel"); + elm_object_part_content_set(p, "button1", bcancel); + evas_object_smart_callback_add(bcancel, "clicked", _on_cancel, c); - e_dialog_title_set(dia, "Connect to Hidden Network"); - e_dialog_icon_set(dia, "network-wireless", 48); + Evas_Object *bok = elm_button_add(p); + elm_object_text_set(bok, "Connect"); + elm_object_part_content_set(p, "button2", bok); + evas_object_smart_callback_add(bok, "clicked", _on_ok, c); - /* Create content list */ - list = e_widget_list_add(evas_object_evas_get(dia->win), 0, 0); + evas_object_event_callback_add(win, EVAS_CALLBACK_DEL, _on_del, c); - /* SSID label and entry */ - o = e_widget_label_add(evas_object_evas_get(dia->win), "Network Name (SSID):"); - e_widget_list_object_append(list, o, 1, 1, 0.5); - - ssid_entry = e_widget_entry_add(evas_object_evas_get(dia->win), &hd->ssid, NULL, NULL, NULL); - e_widget_size_min_set(ssid_entry, 280, 30); - e_widget_list_object_append(list, ssid_entry, 1, 1, 0.5); - hd->ssid_entry = ssid_entry; - - /* Spacing */ - o = e_widget_label_add(evas_object_evas_get(dia->win), " "); - e_widget_list_object_append(list, o, 1, 1, 0.5); - - /* Passphrase label and entry */ - o = e_widget_label_add(evas_object_evas_get(dia->win), "Passphrase (leave empty for open network):"); - e_widget_list_object_append(list, o, 1, 1, 0.5); - - pass_entry = e_widget_entry_add(evas_object_evas_get(dia->win), &hd->passphrase, NULL, NULL, NULL); - e_widget_entry_password_set(pass_entry, 1); - e_widget_size_min_set(pass_entry, 280, 30); - e_widget_list_object_append(list, pass_entry, 1, 1, 0.5); - hd->pass_entry = pass_entry; - - e_dialog_content_set(dia, list, 300, 180); - - /* Buttons */ - e_dialog_button_add(dia, "Connect", NULL, _hidden_dialog_ok_cb, hd); - e_dialog_button_add(dia, "Cancel", NULL, _hidden_dialog_cancel_cb, hd); - - e_dialog_button_focus_num(dia, 0); - e_dialog_show(dia); - - INF("Hidden network dialog shown"); -} - -/* OK button callback */ -static void -_hidden_dialog_ok_cb(void *data, E_Dialog *dialog EINA_UNUSED) -{ - Hidden_Dialog *hd = data; - - if (!hd) return; - - DBG("Hidden network dialog OK clicked"); - - if (!hd->ssid || strlen(hd->ssid) == 0) - { - e_util_dialog_show("Error", "Please enter a network name (SSID)."); - return; - } - - /* Check if passphrase is provided */ - hd->has_password = (hd->passphrase && strlen(hd->passphrase) > 0); - - if (hd->has_password && strlen(hd->passphrase) < 8) - { - e_util_dialog_show("Error", - "Passphrase must be at least 8 characters long."); - return; - } - - /* Store passphrase if provided */ - if (hd->has_password) - { - iwd_agent_set_passphrase(hd->passphrase); - } - - /* Connect to hidden network */ - if (hd->inst && hd->inst->device) - { - INF("Connecting to hidden network: %s", hd->ssid); - iwd_device_connect_hidden(hd->inst->device, hd->ssid); - } - - /* Close dialog */ - _hidden_dialog_free(hd); -} - -/* Cancel button callback */ -static void -_hidden_dialog_cancel_cb(void *data, E_Dialog *dialog EINA_UNUSED) -{ - Hidden_Dialog *hd = data; - - DBG("Hidden network dialog cancelled"); - - _hidden_dialog_free(hd); -} - -/* Free hidden dialog */ -static void -_hidden_dialog_free(Hidden_Dialog *hd) -{ - if (!hd) return; - - DBG("Freeing hidden network dialog"); - - if (hd->dialog) - e_object_del(E_OBJECT(hd->dialog)); - - /* Clear sensitive data from memory */ - if (hd->ssid) - { - memset(hd->ssid, 0, strlen(hd->ssid)); - E_FREE(hd->ssid); - } - - if (hd->passphrase) - { - memset(hd->passphrase, 0, strlen(hd->passphrase)); - E_FREE(hd->passphrase); - } - - E_FREE(hd); - hidden_dialog = NULL; -} - -/* Cancel any open hidden dialog */ -void -wifi_hidden_dialog_cancel(void) -{ - if (hidden_dialog) - { - DBG("Cancelling hidden network dialog from external request"); - _hidden_dialog_free(hidden_dialog); - } + evas_object_show(p); + evas_object_show(win); + elm_object_focus_set(c->e_ssid, EINA_TRUE); } diff --git a/src/ui/wifi_hidden.h b/src/ui/wifi_hidden.h index 5e9c4b1..09b4dde 100644 --- a/src/ui/wifi_hidden.h +++ b/src/ui/wifi_hidden.h @@ -1,15 +1,13 @@ #ifndef WIFI_HIDDEN_H #define WIFI_HIDDEN_H -#include +#include -/* Forward declarations */ -typedef struct _Instance Instance; +/* Called once with ok=EINA_TRUE + ssid (and optional passphrase, may be ""), + * or ok=EINA_FALSE on cancel. The dialog destroys itself. */ +typedef void (*Wifi_Hidden_Cb)(void *data, const char *ssid, + const char *passphrase, Eina_Bool ok); -/* Show hidden network connection dialog */ -void wifi_hidden_dialog_show(Instance *inst); - -/* Cancel/close hidden network dialog */ -void wifi_hidden_dialog_cancel(void); +void wifi_hidden_prompt(Evas_Object *parent, Wifi_Hidden_Cb cb, void *data); #endif diff --git a/src/ui/wifi_list.c b/src/ui/wifi_list.c new file mode 100644 index 0000000..2717a01 --- /dev/null +++ b/src/ui/wifi_list.c @@ -0,0 +1,13 @@ +#include "wifi_list.h" + +/* TODO: Genlist of networks, sorted (known first, then signal desc), + * with security icon, signal bars, and click → connect/auth flow. */ + +Evas_Object * +wifi_list_add(Evas_Object *parent) +{ + Evas_Object *gl = elm_genlist_add(parent); + return gl; +} + +void wifi_list_refresh(Evas_Object *list EINA_UNUSED) { /* TODO */ } diff --git a/src/ui/wifi_list.h b/src/ui/wifi_list.h new file mode 100644 index 0000000..cfc0bcf --- /dev/null +++ b/src/ui/wifi_list.h @@ -0,0 +1,9 @@ +#ifndef WIFI_LIST_H +#define WIFI_LIST_H + +#include + +Evas_Object *wifi_list_add(Evas_Object *parent); +void wifi_list_refresh(Evas_Object *list); + +#endif diff --git a/src/ui/wifi_status.c b/src/ui/wifi_status.c new file mode 100644 index 0000000..1f61cfe --- /dev/null +++ b/src/ui/wifi_status.c @@ -0,0 +1,12 @@ +#include "wifi_status.h" + +/* TODO: current connection summary widget (SSID, signal, IP, Disconnect). */ + +Evas_Object * +wifi_status_add(Evas_Object *parent) +{ + Evas_Object *box = elm_box_add(parent); + return box; +} + +void wifi_status_refresh(Evas_Object *o EINA_UNUSED) { /* TODO */ } diff --git a/src/ui/wifi_status.h b/src/ui/wifi_status.h new file mode 100644 index 0000000..857f386 --- /dev/null +++ b/src/ui/wifi_status.h @@ -0,0 +1,9 @@ +#ifndef WIFI_STATUS_H +#define WIFI_STATUS_H + +#include + +Evas_Object *wifi_status_add(Evas_Object *parent); +void wifi_status_refresh(Evas_Object *o); + +#endif