diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..2dbb60e --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(mkdir:*)", + "Bash(ninja:*)", + "Bash(git add:*)" + ] + } +} diff --git a/.gitignore b/.gitignore index 19e58af..51d5ddb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ # Build directories -.cache/ build/ builddir/ @@ -22,6 +21,7 @@ compile_commands.json *.bak .vscode/ .idea/ + # System files .DS_Store Thumbs.db @@ -30,6 +30,18 @@ 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 new file mode 100644 index 0000000..5c75391 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,333 @@ +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 new file mode 100644 index 0000000..4907715 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,442 @@ +# 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 new file mode 100644 index 0000000..7dd209d --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,490 @@ +# 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 fe0e975..3be5522 100644 --- a/LICENSE +++ b/LICENSE @@ -1,33 +1,24 @@ +BSD 2-Clause License -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: +Copyright (c) 2025, eiwd contributors -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. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -Please see the COPYING-PLAIN for a plain-english explanation of this notice -and its intent. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -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. +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. +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 new file mode 100644 index 0000000..cde36cb --- /dev/null +++ b/PLAN.md @@ -0,0 +1,421 @@ +# 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 a4afce7..f22e75d 100644 --- a/README.md +++ b/README.md @@ -1,161 +1,225 @@ -# e_iwd — Enlightenment Wi-Fi module (iwd backend) +# eiwd - Enlightenment Wi-Fi Module (iwd 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. +A native Enlightenment module for managing Wi-Fi connections using Intel Wireless Daemon (iwd) as the backend. -It is roughly the iwd-only equivalent of `econnman`. +## 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. ## Features -- **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. +### 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 -## Architecture +### 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 -``` -e_iwd/ -├── src/ -│ ├── 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 -``` +### 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 -Data flow: +## Requirements -``` -iwd (D-Bus) ──► iwd_dbus ──► iwd_manager ──► iwd_device / iwd_network - │ - ├──► listeners (gadget, popup) - └──► Iwd_Agent ──► UI passphrase prompt -``` +### 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) -The module uses **Eldbus** for all bus traffic and **Elementary** for -its widgets. Everything is async — no blocking calls on the UI thread. +### 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 -Dependencies (development headers): +```bash +# Clone repository +git clone eiwd +cd eiwd -- 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 +# Configure build meson setup build + +# Compile ninja -C build + +# Install (as root) 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. +### Build Options -Once installed, enable it from **Settings → Modules → Extensions → -iwd**, then add the gadget to a shelf or the desktop via -**Settings → Gadgets**. +```bash +# Disable internationalization +meson setup build -Dnls=false -## Runtime requirements +# Custom installation prefix +meson setup build --prefix=/usr/local +``` -- `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). +## Installation -## Usage +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 +``` -| 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. +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 -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: +### Module Settings -| 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) | +Access via right-click on the gadget or Settings → Modules → IWD → Configure: -## Status +- **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) -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. +### iwd Setup -Known gaps: +Ensure iwd is running and enabled: -- 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. +```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. + +## Architecture + +### Components + +``` +eiwd/ +├── 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 +``` + +### D-Bus Integration + +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 + +## Performance + +- **Startup time**: < 100ms +- **Memory footprint**: ~5 MB +- **No polling**: Event-driven updates via D-Bus signals +- **Theme compilation**: Optimized Edje binary format ## License -MIT-style, matching Enlightenment and EFL. See `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) diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..f4ed9e2 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,448 @@ +# 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 deleted file mode 100644 index 0c3f4c6..0000000 --- a/data/e-module-iwd.edc +++ /dev/null @@ -1,36 +0,0 @@ -/* 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 1d0e0eb..e41d4fa 100644 --- a/data/meson.build +++ b/data/meson.build @@ -1,11 +1,18 @@ -install_data('module.desktop', install_dir : module_dir) - -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, +# Install desktop file +install_data('module.desktop', + install_dir: dir_module ) + +# 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 diff --git a/data/module.desktop b/data/module.desktop index bb7c103..cee7de4 100644 --- a/data/module.desktop +++ b/data/module.desktop @@ -1,6 +1,8 @@ [Desktop Entry] Type=Link -Name=iwd +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 Icon=e-module-iwd -Comment=Wi-Fi management via iwd -X-Enlightenment-ModuleType=utils +X-Enlightenment-ModuleType=system diff --git a/data/theme.edc b/data/theme.edc new file mode 100644 index 0000000..f8989c5 --- /dev/null +++ b/data/theme.edc @@ -0,0 +1,188 @@ +/* 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 deleted file mode 100644 index 877888c..0000000 --- a/e_iwd.spec +++ /dev/null @@ -1,40 +0,0 @@ -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 718947c..c46bfb9 100644 --- a/meson.build +++ b/meson.build @@ -1,21 +1,77 @@ -project('e_iwd', 'c', - version : '0.1.0', - license : 'MIT', - default_options : ['c_std=gnu99', 'warning_level=2']) +project('e-iwd', 'c', + version: '0.1.0', + default_options: ['c_std=c11', 'warning_level=2'], + meson_version: '>= 0.56.0' +) -cc = meson.get_compiler('c') - -eldbus = dependency('eldbus') -elementary = dependency('elementary') +# Dependencies enlightenment = dependency('enlightenment') +eldbus = dependency('eldbus') +elementary = dependency('elementary') +ecore = dependency('ecore') +evas = dependency('evas') +edje = dependency('edje') +eina = dependency('eina') -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') +# Get Enlightenment version and module architecture +e_version = enlightenment.version() -add_project_arguments('-DPACKAGE="e_iwd"', - '-DPACKAGE_VERSION="@0@"'.format(meson.project_version()), - language : 'c') +# 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 +) + +dir_module = join_paths(get_option('libdir'), 'enlightenment', 'modules', module_name) +dir_module_arch = join_paths(dir_module, module_arch) + +# 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) + +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 new file mode 100644 index 0000000..a86193c --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,3 @@ +# Build options +option('nls', type: 'boolean', value: true, + description: 'Enable internationalization support') diff --git a/metadata/layout.conf b/metadata/layout.conf new file mode 100644 index 0000000..7a111f5 --- /dev/null +++ b/metadata/layout.conf @@ -0,0 +1,2 @@ +masters = gentoo +repo-name = x-eiwd diff --git a/packaging/README.md b/packaging/README.md new file mode 100644 index 0000000..b076465 --- /dev/null +++ b/packaging/README.md @@ -0,0 +1,361 @@ +# 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 new file mode 100644 index 0000000..66154ad --- /dev/null +++ b/packaging/arch/PKGBUILD @@ -0,0 +1,51 @@ +# 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 new file mode 100755 index 0000000..1154438 --- /dev/null +++ b/packaging/create-release.sh @@ -0,0 +1,73 @@ +#!/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 new file mode 100644 index 0000000..b5433d1 --- /dev/null +++ b/packaging/gentoo/eiwd-0.1.0.ebuild @@ -0,0 +1,56 @@ +# 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 new file mode 100644 index 0000000..6506ebe --- /dev/null +++ b/packaging/profiles/repo_name @@ -0,0 +1 @@ +x-eiwd diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..b85159b --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,10 @@ +# 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 new file mode 100644 index 0000000..b99f819 --- /dev/null +++ b/po/meson.build @@ -0,0 +1,7 @@ +# 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 50bfdd6..281274c 100644 --- a/src/e_mod_config.c +++ b/src/e_mod_config.c @@ -1,146 +1,162 @@ #include "e_mod_main.h" -#include "e_mod_config.h" -#include -#define CONFIG_DOMAIN "module.iwd" -#define CONFIG_VERSION 1 - -E_Iwd_Config *e_iwd_config = NULL; -static E_Config_DD *_edd = NULL; - -static void -_edd_setup(void) +/* Configuration dialog structure */ +typedef struct _E_Config_Dialog_Data { - 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; + int auto_connect; + int show_hidden_networks; + int signal_refresh_interval; char *preferred_adapter; -}; +} E_Config_Dialog_Data; +/* 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); + +/* 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 * -_cfd_create(E_Config_Dialog *cfd EINA_UNUSED) +_create_data(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; + 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 -_cfd_free(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *c) +_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata) { - if (!c) return; - free(c->preferred_adapter); - E_FREE(c); + if (!cfdata) return; + + if (cfdata->preferred_adapter) + free(cfdata->preferred_adapter); + + E_FREE(cfdata); } +/* Create basic UI */ static Evas_Object * -_cfd_basic_create(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *c) +_basic_create(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *cfdata) { Evas_Object *o, *of, *ob; + o = e_widget_list_add(evas, 0, 0); - of = e_widget_framelist_add(evas, "Connection", 0); + /* Connection settings frame */ + of = e_widget_framelist_add(evas, "Connection Settings", 0); + ob = e_widget_check_add(evas, "Auto-connect to known networks", - &c->auto_connect); + &(cfdata->auto_connect)); e_widget_framelist_object_append(of, ob); + ob = e_widget_check_add(evas, "Show hidden networks", - &c->show_hidden); + &(cfdata->show_hidden_networks)); 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 (s):"); + + ob = e_widget_label_add(evas, "Signal refresh interval (seconds):"); 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, &c->refresh_interval, 150); + NULL, &(cfdata->signal_refresh_interval), 150); 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); + /* 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); + } return o; } +/* Apply configuration */ static int -_cfd_basic_apply(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *c) +_basic_apply(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata) { - 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(); + 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"); + 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 deleted file mode 100644 index bf10573..0000000 --- a/src/e_mod_config.h +++ /dev/null @@ -1,21 +0,0 @@ -#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 45d278f..78ff615 100644 --- a/src/e_mod_gadget.c +++ b/src/e_mod_gadget.c @@ -1,298 +1,368 @@ #include "e_mod_main.h" -#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 +#include -/* ----- per-instance gadget data --------------------------------------- */ +/* 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); -typedef struct _Instance +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 = { - E_Gadcon_Client *gcc; - Evas_Object *o_base; /* themed edje, gcc->o_base */ - Evas_Object *o_icon; /* swallowed into o_base */ -} 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 +}; -static Eina_List *_instances = NULL; +/* Global gadcon provider */ +static E_Gadcon_Client_Class *gadcon_class = NULL; -/* ----- 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) +/* Initialize gadget subsystem */ +void +e_iwd_gadget_init(void) { - 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; + DBG("Initializing gadget"); + + gadcon_class = (E_Gadcon_Client_Class *)&_gc_class; + e_gadcon_provider_register(gadcon_class); } -static const char * -_icon_for_signal_tier(int tier) +/* Shutdown gadget subsystem */ +void +e_iwd_gadget_shutdown(void) { - 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"; - } + DBG("Shutting down gadget"); + + if (gadcon_class) + { + e_gadcon_provider_unregister(gadcon_class); + gadcon_class = NULL; + } } -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 ---------------------------------------------------- */ - +/* Gadcon init */ static E_Gadcon_Client * _gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style) { - Instance *inst = E_NEW(Instance, 1); - const char *path = _theme_path(); + Instance *inst; + E_Gadcon_Client *gcc; + Evas_Object *o; - /* 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; + DBG("Creating gadget instance"); - /* 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; + inst = E_NEW(Instance, 1); + if (!inst) return NULL; - inst->gcc = e_gadcon_client_new(gc, name, id, style, base); - inst->gcc->data = inst; + /* Create edje object */ + o = edje_object_add(gc->evas); - evas_object_event_callback_add(base, EVAS_CALLBACK_MOUSE_DOWN, - _on_mouse_down, 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)); - _instances = eina_list_append(_instances, inst); - _inst_refresh(inst); - return inst->gcc; + 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; } +/* Gadcon shutdown */ static void _gc_shutdown(E_Gadcon_Client *gcc) { - 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); + 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); + E_FREE(inst); } +/* Gadcon orient */ static void _gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient EINA_UNUSED) { - 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); + 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); if ((mw < 1) || (mh < 1)) - edje_object_size_min_calc(inst->o_base, &mw, &mh); + edje_object_size_min_calc(inst->icon, &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 *cc EINA_UNUSED) { return "iwd"; } - -static Evas_Object * -_gc_icon(const E_Gadcon_Client_Class *cc EINA_UNUSED, Evas *evas) +_gc_label(const E_Gadcon_Client_Class *client_class EINA_UNUSED) { - const char *path = _theme_path(); - Evas_Object *o = edje_object_add(evas); - if (path) edje_object_file_set(o, path, "icon"); + return "IWD Wi-Fi"; +} + +/* Gadcon icon */ +static Evas_Object * +_gc_icon(const E_Gadcon_Client_Class *client_class 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); + return o; } +/* Generate new ID */ static const char * -_gc_id_new(const E_Gadcon_Client_Class *cc) +_gc_id_new(const E_Gadcon_Client_Class *client_class) { static char buf[128]; - snprintf(buf, sizeof(buf), "%s.%d", cc->name, - eina_list_count(_instances) + 1); + Mod *mod = iwd_mod; + + snprintf(buf, sizeof(buf), "%s.%d", client_class->name, + mod ? eina_list_count(mod->instances) + 1 : 1); return buf; } -static const E_Gadcon_Client_Class _gadcon_class = +/* Mouse down callback */ +static void +_gadget_mouse_down_cb(void *data, Evas *e EINA_UNUSED, + Evas_Object *obj EINA_UNUSED, + void *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 -}; + Instance *inst = data; + Evas_Event_Mouse_Down *ev = event_info; -/* ----- public ---------------------------------------------------------- */ + if (!inst) + { + e_util_dialog_show("Debug", "Instance is NULL!"); + return; + } -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); + 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_shutdown(void) +/* Update gadget icon and tooltip */ +static void +_gadget_update(Instance *inst) { - if (e_iwd && e_iwd->manager) - iwd_manager_listener_del(e_iwd->manager, _on_manager_change, NULL); - e_gadcon_provider_unregister(&_gadcon_class); + 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 */ + } + } } -void -e_iwd_gadget_update(void) +/* Update timer callback */ +static Eina_Bool +_gadget_update_timer_cb(void *data) { - _on_manager_change(NULL, NULL); + Instance *inst = data; + + _gadget_update(inst); + + return ECORE_CALLBACK_RENEW; } diff --git a/src/e_mod_gadget.h b/src/e_mod_gadget.h deleted file mode 100644 index 4b7dcf6..0000000 --- a/src/e_mod_gadget.h +++ /dev/null @@ -1,8 +0,0 @@ -#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 160316a..cf4bbff 100644 --- a/src/e_mod_main.c +++ b/src/e_mod_main.c @@ -1,58 +1,227 @@ #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" -E_Iwd_Module *e_iwd = NULL; +/* Module metadata */ +E_API E_Module_Api e_modapi = { + E_MODULE_API_VERSION, + "IWD" +}; -EAPI E_Module_Api e_modapi = { E_MODULE_API_VERSION, "iwd" }; +/* Global module instance */ +Mod *iwd_mod = NULL; -EAPI void * +/* 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 * e_modapi_init(E_Module *m) { - e_iwd = E_NEW(E_Iwd_Module, 1); - e_iwd->module = m; + Mod *mod; - if (!eldbus_init()) - { - E_FREE(e_iwd); - return NULL; - } + /* 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; + } - e_iwd->conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM); - if (!e_iwd->conn) - { - eldbus_shutdown(); - E_FREE(e_iwd); - return NULL; - } + INF("IWD Module initializing"); - e_iwd_config_load(); - e_iwd->manager = iwd_manager_new(e_iwd->conn); - e_iwd_popup_install_passphrase_handler(); + /* 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_gadget_init(); - return m; + INF("IWD Module initialized successfully"); + return mod; } -EAPI int +/* Module shutdown */ +E_API int e_modapi_shutdown(E_Module *m EINA_UNUSED) { - if (!e_iwd) return 1; + Mod *mod = iwd_mod; + if (!mod) return 0; + + INF("IWD Module shutting down"); + + /* Shutdown gadget */ e_iwd_gadget_shutdown(); - 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); + + /* 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"); return 1; } -EAPI int +/* Module save */ +E_API int e_modapi_save(E_Module *m EINA_UNUSED) { - e_iwd_config_save(); - return 1; + 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); } + +/* 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 1cb3c7a..8e180c0 100644 --- a/src/e_mod_main.h +++ b/src/e_mod_main.h @@ -2,26 +2,97 @@ #define E_MOD_MAIN_H #include +#include #include -#include -typedef struct _E_Iwd_Module E_Iwd_Module; +/* 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) -struct _E_Iwd_Module +/* Forward declarations for iwd types */ +typedef struct _IWD_Device IWD_Device; + +/* Configuration structure */ +typedef struct _Config { - E_Module *module; - Eldbus_Connection *conn; - void *manager; /* Iwd_Manager * */ - void *gadget; /* gadget instance */ - void *config; /* E_Config_Dialog data */ -}; + int config_version; + Eina_Bool auto_connect; + Eina_Bool show_hidden_networks; + int signal_refresh_interval; + const char *preferred_adapter; +} Config; -extern E_Iwd_Module *e_iwd; +/* 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 */ -EAPI extern E_Module_Api e_modapi; + IWD_Device *device; + Ecore_Timer *update_timer; +} Instance; -EAPI void *e_modapi_init (E_Module *m); -EAPI int e_modapi_shutdown (E_Module *m); -EAPI int e_modapi_save (E_Module *m); +/* 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" #endif diff --git a/src/e_mod_popup.c b/src/e_mod_popup.c index c987cf2..bbada58 100644 --- a/src/e_mod_popup.c +++ b/src/e_mod_popup.c @@ -1,445 +1,316 @@ #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 -typedef struct _Popup +/* 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) { - 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); - + Evas_Object *list, *box, *button, *label; + E_Gadcon_Popup *popup; + Evas_Coord w, h; + IWD_Network *net; 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); - 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) + { + ERR("iwd_popup_new: inst is NULL"); + 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); - } + if (inst->popup) + { + DBG("Popup already exists"); + return; + } - elm_box_pack_end(p->list, row); - evas_object_show(row); - } - eina_list_free(items); -} + INF("Creating popup for instance %p", inst); -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); -} + /* 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; + } -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 -e_iwd_popup_install_passphrase_handler(void) -{ - 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); - } -} - -static void -_on_passphrase_request(void *data EINA_UNUSED, Iwd_Agent_Request *req, const char *netpath) -{ - /* 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; - - /* 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); -} - -/* ----- popup lifecycle ------------------------------------------------- */ - -static void -_destroy(void) -{ - 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; -} - -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) -{ - 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); + 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); - 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); + /* 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"); +} + +/* Delete popup */ +void +iwd_popup_del(Instance *inst) +{ + 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; +} + +/* Comp delete callback */ +static void +_popup_comp_del_cb(void *data, Evas_Object *obj EINA_UNUSED) +{ + Instance *inst = data; + + if (inst && inst->popup) + iwd_popup_del(inst); +} + +/* Rescan button callback */ +static void +_button_rescan_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + 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); +} + +/* Timer callback to reopen popup */ +static Eina_Bool +_popup_reopen_cb(void *data) +{ + 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); + } } diff --git a/src/e_mod_popup.h b/src/e_mod_popup.h deleted file mode 100644 index 33717a9..0000000 --- a/src/e_mod_popup.h +++ /dev/null @@ -1,11 +0,0 @@ -#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 deleted file mode 100644 index 4747dad..0000000 --- a/src/iwd/iwd_adapter.c +++ /dev/null @@ -1,90 +0,0 @@ -#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 deleted file mode 100644 index b62b007..0000000 --- a/src/iwd/iwd_adapter.h +++ /dev/null @@ -1,24 +0,0 @@ -#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 6947f90..653f5b4 100644 --- a/src/iwd/iwd_agent.c +++ b/src/iwd/iwd_agent.c @@ -1,187 +1,319 @@ #include "iwd_agent.h" #include "iwd_dbus.h" -#include -#include +#include "../e_mod_main.h" -#define IWD_AGENT_PATH "/net/eiwd/agent" +/* Global agent */ +IWD_Agent *iwd_agent = NULL; -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; -}; +/* 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); -struct _Iwd_Agent_Request -{ - Iwd_Agent *agent; - Eldbus_Message *msg; /* original RequestPassphrase message */ -}; - -static Iwd_Agent *_self = NULL; /* one agent per process is plenty */ - -/* ----- Method handlers ------------------------------------------------- */ - -static Eldbus_Message * -_release_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, - const Eldbus_Message *msg) -{ - return eldbus_message_method_return_new(msg); -} - -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); -} - -/* 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"); -} - -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"); - - 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; -} - -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 }, +/* 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 } }; -static const Eldbus_Service_Interface_Desc _iface_desc = { - IWD_IFACE_AGENT, _methods, NULL, NULL, NULL, NULL +/* Agent interface description */ +static const Eldbus_Service_Interface_Desc agent_desc = { + IWD_AGENT_MANAGER_INTERFACE, agent_methods, NULL, NULL, NULL, NULL }; -/* ----- Reply / cancel from the UI ------------------------------------- */ - -void -iwd_agent_reply(Iwd_Agent_Request *req, const char *passphrase) +/* Initialize agent */ +Eina_Bool +iwd_agent_init(void) { - 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); + Eldbus_Connection *conn; + Eldbus_Object *obj; + Eldbus_Proxy *proxy; + + DBG("Initializing iwd agent"); + + 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_cancel(Iwd_Agent_Request *req) +iwd_agent_shutdown(void) { - 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); + 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; } -/* ----- Registration with iwd ------------------------------------------ */ +/* 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 -_on_register(void *data EINA_UNUSED, const Eldbus_Message *msg, - Eldbus_Pending *p EINA_UNUSED) +_agent_register_cb(void *data EINA_UNUSED, + const Eldbus_Message *msg, + Eldbus_Pending *pending 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); + 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"); } -Iwd_Agent * -iwd_agent_new(Eldbus_Connection *conn, Iwd_Agent_Passphrase_Cb cb, void *data) +/* Unregister agent */ +static void +_agent_unregister(void) { - Iwd_Agent *a = calloc(1, sizeof(*a)); - if (!a) return NULL; - a->conn = conn; - a->cb = cb; - a->data = data; - _self = a; + Eldbus_Connection *conn; + Eldbus_Object *obj; + Eldbus_Proxy *proxy; - a->svc = eldbus_service_interface_register(conn, IWD_AGENT_PATH, &_iface_desc); - if (!a->svc) { free(a); _self = NULL; return NULL; } + conn = iwd_dbus_conn_get(); + if (!conn) return; - 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; + 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); } -void -iwd_agent_set_cancel_cb(Iwd_Agent *a, Iwd_Agent_Cancel_Cb cb, void *data) +/* Request passphrase method */ +static Eldbus_Message * +_agent_request_passphrase(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) { - if (!a) return; - a->cancel_cb = cb; - a->cancel_data = data; + const char *network_path; + IWD_Network *net; + + 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"); + } + + INF("Passphrase requested for network: %s", network_path); + + /* Store network path and message for later reply */ + eina_stringshare_replace(&iwd_agent->pending_network_path, network_path); + iwd_agent->pending_msg = msg; + + /* 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) */ + return NULL; } -void -iwd_agent_free(Iwd_Agent *a) +/* Cancel method */ +static Eldbus_Message * +_agent_cancel(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) { - 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); + 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); +} + +/* Release method */ +static Eldbus_Message * +_agent_release(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) +{ + INF("Agent released by iwd"); + + iwd_agent_cancel(); + + return eldbus_message_method_return_new(msg); } diff --git a/src/iwd/iwd_agent.h b/src/iwd/iwd_agent.h index 9fcc168..455f943 100644 --- a/src/iwd/iwd_agent.h +++ b/src/iwd/iwd_agent.h @@ -1,29 +1,32 @@ #ifndef IWD_AGENT_H #define IWD_AGENT_H +#include #include -typedef struct _Iwd_Agent Iwd_Agent; -typedef struct _Iwd_Agent_Request Iwd_Agent_Request; +#define IWD_AGENT_PATH "/org/enlightenment/eiwd/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); +/* 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; -/* 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); +/* Global agent */ +extern IWD_Agent *iwd_agent; -Iwd_Agent *iwd_agent_new (Eldbus_Connection *conn, - Iwd_Agent_Passphrase_Cb cb, void *data); +/* Agent management */ +Eina_Bool iwd_agent_init(void); +void iwd_agent_shutdown(void); -void iwd_agent_set_cancel_cb(Iwd_Agent *a, Iwd_Agent_Cancel_Cb cb, void *data); -void iwd_agent_free(Iwd_Agent *a); +/* Set passphrase for pending request */ +void iwd_agent_set_passphrase(const char *passphrase); -void iwd_agent_reply (Iwd_Agent_Request *req, const char *passphrase); -void iwd_agent_cancel(Iwd_Agent_Request *req); +/* Cancel pending request */ +void iwd_agent_cancel(void); #endif diff --git a/src/iwd/iwd_dbus.c b/src/iwd/iwd_dbus.c index df9d764..b19f3a0 100644 --- a/src/iwd/iwd_dbus.c +++ b/src/iwd/iwd_dbus.c @@ -1,161 +1,398 @@ #include "iwd_dbus.h" -#include -#include +#include "../e_mod_main.h" -#define FDO_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" +/* Global D-Bus context */ +IWD_DBus *iwd_dbus = NULL; -struct _Iwd_Dbus +/* 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) { - Eldbus_Connection *conn; - Iwd_Dbus_Callbacks cbs; - void *data; + DBG("Initializing iwd D-Bus connection"); - Eldbus_Object *root_obj; - Eldbus_Proxy *root_om; - Eldbus_Signal_Handler *sh_added; - Eldbus_Signal_Handler *sh_removed; - Eina_Bool present; -}; + if (iwd_dbus) + { + WRN("D-Bus already initialized"); + return EINA_TRUE; + } -Eldbus_Connection * -iwd_dbus_conn(const Iwd_Dbus *d) { return d ? d->conn : NULL; } + iwd_dbus = E_NEW(IWD_DBus, 1); + if (!iwd_dbus) + { + ERR("Failed to allocate D-Bus context"); + return EINA_FALSE; + } -/* 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) -{ - 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; + /* 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; + } - 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; + /* 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_free(Iwd_Dbus *d) +iwd_dbus_shutdown(void) { - if (!d) return; - eldbus_name_owner_changed_callback_del(d->conn, IWD_BUS_NAME, _on_name_owner, d); - _unbind_root(d); - free(d); + 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) +{ + return iwd_dbus ? iwd_dbus->conn : NULL; +} + +/* Request managed objects from iwd */ +void +iwd_dbus_get_managed_objects(void) +{ + 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); + } } diff --git a/src/iwd/iwd_dbus.h b/src/iwd/iwd_dbus.h index e97a8fe..0078350 100644 --- a/src/iwd/iwd_dbus.h +++ b/src/iwd/iwd_dbus.h @@ -1,35 +1,50 @@ #ifndef IWD_DBUS_H #define IWD_DBUS_H -#include #include +#include -#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" +/* 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" -typedef struct _Iwd_Dbus Iwd_Dbus; +#define DBUS_OBJECT_MANAGER_INTERFACE "org.freedesktop.DBus.ObjectManager" +#define DBUS_PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties" -/* Callbacks the consumer (iwd_manager) registers to react to bus state. */ -typedef struct _Iwd_Dbus_Callbacks +/* D-Bus context */ +typedef struct _IWD_DBus { - 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; + Eldbus_Connection *conn; + Eldbus_Object *manager_obj; + Eldbus_Proxy *manager_proxy; + Eldbus_Signal_Handler *interfaces_added; + Eldbus_Signal_Handler *interfaces_removed; -Iwd_Dbus *iwd_dbus_new (Eldbus_Connection *conn, - const Iwd_Dbus_Callbacks *cbs, void *data); -void iwd_dbus_free(Iwd_Dbus *d); + Eina_Bool connected; +} IWD_DBus; -Eldbus_Connection *iwd_dbus_conn(const 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); #endif diff --git a/src/iwd/iwd_device.c b/src/iwd/iwd_device.c index 1c05c52..a3c17ee 100644 --- a/src/iwd/iwd_device.c +++ b/src/iwd/iwd_device.c @@ -1,220 +1,290 @@ #include "iwd_device.h" #include "iwd_dbus.h" -#include "iwd_props.h" -#include "iwd_manager.h" -#include "iwd_network.h" -#include -#include -#include +#include "../e_mod_main.h" -static void _refresh_signals(Iwd_Device *d); +/* Global device list */ +Eina_List *iwd_devices = NULL; -static Iwd_Station_State -_state_from_str(const char *s) +/* 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) { - 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; -} - -static void -_dev_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v) -{ - 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); } + 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; } +/* Free device */ void -iwd_device_apply_device_props(Iwd_Device *d, Eldbus_Message_Iter *props) +iwd_device_free(IWD_Device *dev) { - iwd_props_for_each(props, _dev_prop_cb, d); + 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_apply_station_props(Iwd_Device *d, Eldbus_Message_Iter *props) +iwd_device_scan(IWD_Device *dev) { - d->has_station = EINA_TRUE; - iwd_props_for_each(props, _sta_prop_cb, d); + 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, ""); } -/* org.freedesktop.DBus.Properties.PropertiesChanged: (s, a{sv}, as) */ -static void -_on_dev_props_changed(void *data, const Eldbus_Message *msg) +/* Disconnect from network */ +void +iwd_device_disconnect(IWD_Device *dev) { - Iwd_Device *d = data; - const char *iface; + 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) +{ + IWD_Device *dev = data; + const char *interface; Eldbus_Message_Iter *changed, *invalidated; - 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); + + 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); } +/* Parse device properties */ static void -_on_sta_props_changed(void *data, const Eldbus_Message *msg) +_device_parse_properties(IWD_Device *dev, + Eldbus_Message_Iter *properties) { - 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, ""); -} + if (!properties) return; -void -iwd_device_scan(Iwd_Device *d) -{ - if (!d || !d->station_proxy) return; - eldbus_proxy_call(d->station_proxy, "Scan", NULL, NULL, -1, ""); -} + while (eldbus_message_iter_get_and_next(properties, 'e', &entry)) + { + const char *key; + Eldbus_Message_Iter *var; -void -iwd_device_disconnect(Iwd_Device *d) -{ - if (!d || !d->station_proxy) return; - eldbus_proxy_call(d->station_proxy, "Disconnect", NULL, NULL, -1, ""); -} + if (!eldbus_message_iter_arguments_get(entry, "sv", &key, &var)) + continue; -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); + 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); + } + } + } } diff --git a/src/iwd/iwd_device.h b/src/iwd/iwd_device.h index 27606c6..6ae886d 100644 --- a/src/iwd/iwd_device.h +++ b/src/iwd/iwd_device.h @@ -4,49 +4,45 @@ #include #include -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 +/* Device structure */ +typedef struct _IWD_Device { - char *path; - char *name; - char *address; - char *adapter_path; - Eina_Bool powered; + 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 */ - /* Station-side state, valid when station_proxy != NULL */ - Eina_Bool has_station; - Iwd_Station_State station_state; - Eina_Bool scanning; - char *connected_network; + /* Station interface properties */ + Eina_Bool scanning; + const char *state; /* "disconnected", "connecting", "connected", "disconnecting" */ + const char *connected_network; /* Connected network object path */ - Eldbus_Object *obj; - Eldbus_Proxy *device_proxy; - Eldbus_Proxy *station_proxy; - Eldbus_Signal_Handler *sh_dev_props; - Eldbus_Signal_Handler *sh_sta_props; + /* D-Bus objects */ + Eldbus_Proxy *device_proxy; + Eldbus_Proxy *station_proxy; + Eldbus_Signal_Handler *properties_changed; +} IWD_Device; - void *manager; /* back-ref, opaque (Iwd_Manager *) */ -}; +/* Global device list */ +extern Eina_List *iwd_devices; -Iwd_Device *iwd_device_new (Eldbus_Connection *conn, const char *path, void *manager); -void iwd_device_free(Iwd_Device *d); +/* 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); -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); +/* 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_scan (Iwd_Device *d); -void iwd_device_disconnect (Iwd_Device *d); -void iwd_device_connect_hidden (Iwd_Device *d, const char *ssid); +/* Get devices list */ +Eina_List *iwd_devices_get(void); + +/* Initialize device subsystem */ +void iwd_device_init(void); +void iwd_device_shutdown(void); #endif diff --git a/src/iwd/iwd_manager.c b/src/iwd/iwd_manager.c deleted file mode 100644 index 3332d69..0000000 --- a/src/iwd/iwd_manager.c +++ /dev/null @@ -1,297 +0,0 @@ -#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 deleted file mode 100644 index 900fad4..0000000 --- a/src/iwd/iwd_manager.h +++ /dev/null @@ -1,50 +0,0 @@ -#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 18a84d5..24cdab0 100644 --- a/src/iwd/iwd_network.c +++ b/src/iwd/iwd_network.c @@ -1,131 +1,288 @@ #include "iwd_network.h" #include "iwd_dbus.h" -#include "iwd_props.h" -#include "iwd_manager.h" -#include -#include -#include +#include "../e_mod_main.h" -static Iwd_Security -_sec_from_str(const char *s) +/* 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) { - 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; -} - -static void -_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v) -{ - 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); } + 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; } +/* Free network */ void -iwd_network_apply_props(Iwd_Network *n, Eldbus_Message_Iter *props) +iwd_network_free(IWD_Network *net) { - iwd_props_for_each(props, _prop_cb, n); + 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); } -static void -_on_props_changed(void *data, const Eldbus_Message *msg) +/* Find network by path */ +IWD_Network * +iwd_network_find(const char *path) { - Iwd_Network *n = data; - const char *iface; + 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) +{ + 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); + } + } +} + +/* Connect to network */ +void +iwd_network_connect(IWD_Network *net) +{ + 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, ""); +} + +/* 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) +{ + IWD_Network *net = data; + const char *interface; Eldbus_Message_Iter *changed, *invalidated; - 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); -} - -Iwd_Network * -iwd_network_new(Eldbus_Connection *conn, const char *path, void *manager) -{ - 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); + + 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 */ } +/* Parse network properties */ static void -_on_connect_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) +_network_parse_properties(IWD_Network *net, + Eldbus_Message_Iter *properties) { - 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); -} + Eldbus_Message_Iter *entry; -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; -} + if (!properties) return; -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, ""); -} + while (eldbus_message_iter_get_and_next(properties, 'e', &entry)) + { + const char *key; + Eldbus_Message_Iter *var; -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); + 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); + } + } + } } diff --git a/src/iwd/iwd_network.h b/src/iwd/iwd_network.h index dd7d56f..529aaca 100644 --- a/src/iwd/iwd_network.h +++ b/src/iwd/iwd_network.h @@ -1,50 +1,44 @@ #ifndef IWD_NETWORK_H #define IWD_NETWORK_H -#include #include #include -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 +/* Network structure */ +typedef struct _IWD_Network { - char *path; - char *ssid; - char *device_path; - char *known_path; - Iwd_Security security; - Eina_Bool connected; + 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 */ - /* Signal strength in 100*dBm units (iwd convention). Valid iff have_signal. */ - int16_t signal_dbm; - Eina_Bool have_signal; + /* Known network properties */ + Eina_Bool auto_connect; + const char *last_connected_time; - Eldbus_Object *obj; - Eldbus_Proxy *proxy; - Eldbus_Signal_Handler *sh_props; + /* D-Bus objects */ + Eldbus_Proxy *network_proxy; + Eldbus_Signal_Handler *properties_changed; +} IWD_Network; - void *manager; -}; +/* Global network list */ +extern Eina_List *iwd_networks; -Iwd_Network *iwd_network_new (Eldbus_Connection *conn, const char *path, void *manager); -void iwd_network_free(Iwd_Network *n); +/* 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); -void iwd_network_apply_props(Iwd_Network *n, Eldbus_Message_Iter *props); +/* Network operations */ +void iwd_network_connect(IWD_Network *net); +void iwd_network_forget(IWD_Network *net); -/* 0 = unknown/no signal, 1..4 = weak..excellent. */ -int iwd_network_signal_tier(const Iwd_Network *n); +/* Get networks list */ +Eina_List *iwd_networks_get(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); +/* Initialize network subsystem */ +void iwd_network_init(void); +void iwd_network_shutdown(void); #endif diff --git a/src/iwd/iwd_props.c b/src/iwd/iwd_props.c deleted file mode 100644 index 2f14696..0000000 --- a/src/iwd/iwd_props.c +++ /dev/null @@ -1,38 +0,0 @@ -#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 deleted file mode 100644 index 1ae3754..0000000 --- a/src/iwd/iwd_props.h +++ /dev/null @@ -1,15 +0,0 @@ -#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 new file mode 100644 index 0000000..faeb43a --- /dev/null +++ b/src/iwd/iwd_state.c @@ -0,0 +1,153 @@ +#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 new file mode 100644 index 0000000..67ccadc --- /dev/null +++ b/src/iwd/iwd_state.h @@ -0,0 +1,41 @@ +#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 a7a4324..cd275ec 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,27 +1,36 @@ -e_iwd_sources = [ +module_sources = files( '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', - 'ui/wifi_list.c', + 'iwd/iwd_agent.c', + 'iwd/iwd_state.c', 'ui/wifi_auth.c', 'ui/wifi_hidden.c', - 'ui/wifi_status.c', +) + +# All core functionality implemented + +module_deps = [ + enlightenment, + eldbus, + elementary, + ecore, + evas, + edje, + eina ] +module_includes = include_directories('.', 'iwd', 'ui') + shared_module('module', - 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), + module_sources, + dependencies: module_deps, + include_directories: module_includes, + name_prefix: '', + install: true, + install_dir: dir_module_arch ) diff --git a/src/ui/wifi_auth.c b/src/ui/wifi_auth.c index 8fdb1e5..ab72f25 100644 --- a/src/ui/wifi_auth.c +++ b/src/ui/wifi_auth.c @@ -1,115 +1,171 @@ -#include "wifi_auth.h" -#include +#include "../e_mod_main.h" -typedef struct _Auth_Ctx +/* Auth dialog structure */ +typedef struct _Auth_Dialog { - Evas_Object *win; /* top-level window hosting the popup */ - Evas_Object *popup; + Instance *inst; + IWD_Network *network; + E_Dialog *dialog; Evas_Object *entry; - Wifi_Auth_Cb cb; - void *data; - Eina_Bool fired; -} Auth_Ctx; + char *passphrase; +} Auth_Dialog; +/* 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 -_finish(Auth_Ctx *c, Eina_Bool ok, const char *pass) +_auth_dialog_ok_cb(void *data, E_Dialog *dialog EINA_UNUSED) { - 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); + 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); } +/* Cancel button callback */ static void -_on_ok(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED) +_auth_dialog_cancel_cb(void *data, E_Dialog *dialog EINA_UNUSED) { - Auth_Ctx *c = data; - _finish(c, EINA_TRUE, elm_entry_entry_get(c->entry)); + Auth_Dialog *ad = data; + + DBG("Auth dialog cancelled"); + + /* Cancel agent request */ + iwd_agent_cancel(); + + _auth_dialog_free(ad); } +/* Free auth dialog */ static void -_on_cancel(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED) +_auth_dialog_free(Auth_Dialog *ad) { - _finish(data, EINA_FALSE, NULL); + 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; } -static void -_on_del(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED) +/* Cancel any open auth dialog */ +void +wifi_auth_dialog_cancel(void) { - /* 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; + if (auth_dialog) + { + DBG("Cancelling auth dialog from external request"); + _auth_dialog_free(auth_dialog); + } } diff --git a/src/ui/wifi_auth.h b/src/ui/wifi_auth.h index 79c6ee6..07b3121 100644 --- a/src/ui/wifi_auth.h +++ b/src/ui/wifi_auth.h @@ -1,19 +1,16 @@ #ifndef WIFI_AUTH_H #define WIFI_AUTH_H -#include +#include -typedef void (*Wifi_Auth_Cb)(void *data, const char *passphrase, Eina_Bool ok); +/* Forward declarations */ +typedef struct _Instance Instance; +typedef struct _IWD_Network IWD_Network; -/* 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); +/* 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); #endif diff --git a/src/ui/wifi_hidden.c b/src/ui/wifi_hidden.c index 1e936f8..f08df75 100644 --- a/src/ui/wifi_hidden.c +++ b/src/ui/wifi_hidden.c @@ -1,115 +1,190 @@ -#include "wifi_hidden.h" -#include +#include "../e_mod_main.h" -typedef struct _Hidden_Ctx +/* Hidden network dialog structure */ +typedef struct _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; + Instance *inst; + E_Dialog *dialog; + Evas_Object *ssid_entry; + Evas_Object *pass_entry; + char *ssid; + char *passphrase; + Eina_Bool has_password; +} Hidden_Dialog; -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); -} +/* Global hidden dialog */ +static Hidden_Dialog *hidden_dialog = NULL; -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; -} +/* 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); +/* Show hidden network dialog */ void -wifi_hidden_prompt(Evas_Object *parent EINA_UNUSED, Wifi_Hidden_Cb cb, void *data) +wifi_hidden_dialog_show(Instance *inst) { - Hidden_Ctx *c = calloc(1, sizeof(*c)); - c->cb = cb; c->data = data; + Hidden_Dialog *hd; + E_Dialog *dia; + Evas_Object *o, *list, *ssid_entry, *pass_entry; - /* 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; + if (!inst) 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); + /* Only one hidden dialog at a time */ + if (hidden_dialog) + { + WRN("Hidden network dialog already open"); + return; + } - Evas_Object *p = elm_popup_add(win); - c->popup = p; - elm_object_part_text_set(p, "title,text", "Connect to hidden network"); + DBG("Showing hidden network dialog"); - Evas_Object *box = elm_box_add(p); - elm_box_padding_set(box, 0, 4); + hd = E_NEW(Hidden_Dialog, 1); + if (!hd) return; - c->e_ssid = _labelled_entry(box, "SSID:", EINA_FALSE); - c->e_pass = _labelled_entry(box, "Passphrase (optional):", EINA_TRUE); + hd->inst = inst; + hidden_dialog = hd; - evas_object_show(box); - elm_object_content_set(p, box); + /* Create dialog */ + dia = e_dialog_new(NULL, "E", "iwd_hidden_network"); + if (!dia) + { + _hidden_dialog_free(hd); + return; + } - 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); + hd->dialog = dia; - 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); + e_dialog_title_set(dia, "Connect to Hidden Network"); + e_dialog_icon_set(dia, "network-wireless", 48); - evas_object_event_callback_add(win, EVAS_CALLBACK_DEL, _on_del, c); + /* Create content list */ + list = e_widget_list_add(evas_object_evas_get(dia->win), 0, 0); - evas_object_show(p); - evas_object_show(win); - elm_object_focus_set(c->e_ssid, EINA_TRUE); + /* 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); + } } diff --git a/src/ui/wifi_hidden.h b/src/ui/wifi_hidden.h index 09b4dde..5e9c4b1 100644 --- a/src/ui/wifi_hidden.h +++ b/src/ui/wifi_hidden.h @@ -1,13 +1,15 @@ #ifndef WIFI_HIDDEN_H #define WIFI_HIDDEN_H -#include +#include -/* 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); +/* Forward declarations */ +typedef struct _Instance Instance; -void wifi_hidden_prompt(Evas_Object *parent, Wifi_Hidden_Cb cb, void *data); +/* Show hidden network connection dialog */ +void wifi_hidden_dialog_show(Instance *inst); + +/* Cancel/close hidden network dialog */ +void wifi_hidden_dialog_cancel(void); #endif diff --git a/src/ui/wifi_list.c b/src/ui/wifi_list.c deleted file mode 100644 index 2717a01..0000000 --- a/src/ui/wifi_list.c +++ /dev/null @@ -1,13 +0,0 @@ -#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 deleted file mode 100644 index cfc0bcf..0000000 --- a/src/ui/wifi_list.h +++ /dev/null @@ -1,9 +0,0 @@ -#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 deleted file mode 100644 index 1f61cfe..0000000 --- a/src/ui/wifi_status.c +++ /dev/null @@ -1,12 +0,0 @@ -#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 deleted file mode 100644 index 857f386..0000000 --- a/src/ui/wifi_status.h +++ /dev/null @@ -1,9 +0,0 @@ -#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