Compare commits

...

No commits in common. "7ef9b6d3bd1e9f6cda6884de8ebd6ad7cef8da14" and "7a6e205002c6a77bb03c19a7f9a10748df904f43" have entirely different histories.

57 changed files with 6009 additions and 2521 deletions

View file

@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(mkdir:*)",
"Bash(ninja:*)",
"Bash(git add:*)"
]
}
}

14
.gitignore vendored
View file

@ -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.*

333
CLAUDE.md Normal file
View file

@ -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 iwds 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

442
CONTRIBUTING.md Normal file
View file

@ -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 <repository-url> 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 <Eina.h>
#include <Eldbus.h>
/* 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!

490
INSTALL.md Normal file
View file

@ -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 <repository-url> 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
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy group="wheel">
<allow send_destination="net.connman.iwd"/>
<allow send_interface="net.connman.iwd.Agent"/>
</policy>
</busconfig>
```
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)

47
LICENSE
View file

@ -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.

421
PLAN.md Normal file
View file

@ -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

316
README.md
View file

@ -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 DBus 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** WiFi
- **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 <repository-url> 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
`<libdir>/enlightenment/modules/iwd/<module_arch>/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/<profile>/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 04 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)

448
TESTING.md Normal file
View file

@ -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 <check.h>
#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
```

View file

@ -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;
}
}
}
}
}

View file

@ -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

View file

@ -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

188
data/theme.edc Normal file
View file

@ -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 */
}
}
}
}
}

View file

@ -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 <maint@example.invalid> - 0.1.0-1
- Initial scaffolding: D-Bus core, gadcon gadget, popup, agent, config persistence.

View file

@ -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: <system>-<abi>-<cpu>-<version>
# 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')

3
meson_options.txt Normal file
View file

@ -0,0 +1,3 @@
# Build options
option('nls', type: 'boolean', value: true,
description: 'Enable internationalization support')

2
metadata/layout.conf Normal file
View file

@ -0,0 +1,2 @@
masters = gentoo
repo-name = x-eiwd

361
packaging/README.md Normal file
View file

@ -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 <your.email@example.com>
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/)

51
packaging/arch/PKGBUILD Normal file
View file

@ -0,0 +1,51 @@
# Maintainer: Your Name <your.email@example.com>
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"
}

73
packaging/create-release.sh Executable file
View file

@ -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"

View file

@ -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."
}

View file

@ -0,0 +1 @@
x-eiwd

10
po/POTFILES.in Normal file
View file

@ -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

7
po/meson.build Normal file
View file

@ -0,0 +1,7 @@
# i18n support
i18n.gettext('eiwd',
args: [
'--directory=' + meson.source_root(),
'--from-code=UTF-8',
]
)

View file

@ -1,146 +1,162 @@
#include "e_mod_main.h"
#include "e_mod_config.h"
#include <e_config_data.h>
#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);
}

View file

@ -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

View file

@ -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 <e_gadcon.h>
#include <limits.h>
/* ----- 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.<br>"
"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;
}

View file

@ -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

View file

@ -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 */

View file

@ -2,26 +2,97 @@
#define E_MOD_MAIN_H
#include <e.h>
#include <Eina.h>
#include <Eldbus.h>
#include <Elementary.h>
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

View file

@ -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 <e_gadcon_popup.h>
#include <stdlib.h>
#include <string.h>
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, "<b>IWD Wi-Fi Manager</b>");
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);
}
}

View file

@ -1,11 +0,0 @@
#ifndef E_MOD_POPUP_H
#define E_MOD_POPUP_H
#include <e_gadcon.h>
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

View file

@ -1,90 +0,0 @@
#include "iwd_adapter.h"
#include "iwd_dbus.h"
#include "iwd_props.h"
#include "iwd_manager.h"
#include <stdlib.h>
#include <string.h>
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;
}

View file

@ -1,24 +0,0 @@
#ifndef IWD_ADAPTER_H
#define IWD_ADAPTER_H
#include <Eina.h>
#include <Eldbus.h>
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

View file

@ -1,187 +1,319 @@
#include "iwd_agent.h"
#include "iwd_dbus.h"
#include <stdlib.h>
#include <string.h>
#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);
}

View file

@ -1,29 +1,32 @@
#ifndef IWD_AGENT_H
#define IWD_AGENT_H
#include <Eina.h>
#include <Eldbus.h>
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

View file

@ -1,161 +1,398 @@
#include "iwd_dbus.h"
#include <stdlib.h>
#include <string.h>
#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.<br>"
"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);
}
}

View file

@ -1,35 +1,50 @@
#ifndef IWD_DBUS_H
#define IWD_DBUS_H
#include <Eldbus.h>
#include <Eina.h>
#include <Eldbus.h>
#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

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}
}
}
}

View file

@ -4,49 +4,45 @@
#include <Eina.h>
#include <Eldbus.h>
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

View file

@ -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 <stdlib.h>
#include <string.h>
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);
}

View file

@ -1,50 +0,0 @@
#ifndef IWD_MANAGER_H
#define IWD_MANAGER_H
#include <Eldbus.h>
#include <Eina.h>
#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<path, Iwd_Device*> */
const Eina_Hash *iwd_manager_devices (const Iwd_Manager *m);
/* Hash<path, Iwd_Network*> */
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

View file

@ -1,131 +1,288 @@
#include "iwd_network.h"
#include "iwd_dbus.h"
#include "iwd_props.h"
#include "iwd_manager.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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.<br>"
"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.<br>"
"Please check your password and try again.");
}
else
{
char buf[512];
snprintf(buf, sizeof(buf), "Connection error:<br>%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);
}
}
}
}

View file

@ -1,50 +1,44 @@
#ifndef IWD_NETWORK_H
#define IWD_NETWORK_H
#include <stdint.h>
#include <Eina.h>
#include <Eldbus.h>
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

View file

@ -1,38 +0,0 @@
#include "iwd_props.h"
#include <string.h>
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;
}

View file

@ -1,15 +0,0 @@
#ifndef IWD_PROPS_H
#define IWD_PROPS_H
#include <Eldbus.h>
#include <Eina.h>
/* 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

153
src/iwd/iwd_state.c Normal file
View file

@ -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";
}
}

41
src/iwd/iwd_state.h Normal file
View file

@ -0,0 +1,41 @@
#ifndef IWD_STATE_H
#define IWD_STATE_H
#include <Eina.h>
/* 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

View file

@ -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
)

View file

@ -1,115 +1,171 @@
#include "wifi_auth.h"
#include <stdlib.h>
#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:<br>"
"<b>%s</b><br><br>"
"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);
}
}

View file

@ -1,19 +1,16 @@
#ifndef WIFI_AUTH_H
#define WIFI_AUTH_H
#include <Elementary.h>
#include <Eina.h>
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

View file

@ -1,115 +1,190 @@
#include "wifi_hidden.h"
#include <stdlib.h>
#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);
}
}

View file

@ -1,13 +1,15 @@
#ifndef WIFI_HIDDEN_H
#define WIFI_HIDDEN_H
#include <Elementary.h>
#include <Eina.h>
/* 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

View file

@ -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 */ }

View file

@ -1,9 +0,0 @@
#ifndef WIFI_LIST_H
#define WIFI_LIST_H
#include <Elementary.h>
Evas_Object *wifi_list_add(Evas_Object *parent);
void wifi_list_refresh(Evas_Object *list);
#endif

View file

@ -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 */ }

View file

@ -1,9 +0,0 @@
#ifndef WIFI_STATUS_H
#define WIFI_STATUS_H
#include <Elementary.h>
Evas_Object *wifi_status_add(Evas_Object *parent);
void wifi_status_refresh(Evas_Object *o);
#endif