Compare commits
No commits in common. "7ef9b6d3bd1e9f6cda6884de8ebd6ad7cef8da14" and "7a6e205002c6a77bb03c19a7f9a10748df904f43" have entirely different histories.
7ef9b6d3bd
...
7a6e205002
57 changed files with 6009 additions and 2521 deletions
9
.claude/settings.local.json
Normal file
9
.claude/settings.local.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(mkdir:*)",
|
||||||
|
"Bash(ninja:*)",
|
||||||
|
"Bash(git add:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
14
.gitignore
vendored
14
.gitignore
vendored
|
|
@ -1,5 +1,4 @@
|
||||||
# Build directories
|
# Build directories
|
||||||
.cache/
|
|
||||||
build/
|
build/
|
||||||
builddir/
|
builddir/
|
||||||
|
|
||||||
|
|
@ -22,6 +21,7 @@ compile_commands.json
|
||||||
*.bak
|
*.bak
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
# System files
|
# System files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
@ -30,6 +30,18 @@ Thumbs.db
|
||||||
config.h
|
config.h
|
||||||
*.edj
|
*.edj
|
||||||
|
|
||||||
|
# Autotools (if used)
|
||||||
|
.deps/
|
||||||
|
.libs/
|
||||||
|
Makefile
|
||||||
|
Makefile.in
|
||||||
|
*.log
|
||||||
|
*.trs
|
||||||
|
autom4te.cache/
|
||||||
|
config.status
|
||||||
|
configure
|
||||||
|
aclocal.m4
|
||||||
|
|
||||||
# Core dumps
|
# Core dumps
|
||||||
core
|
core
|
||||||
core.*
|
core.*
|
||||||
|
|
|
||||||
333
CLAUDE.md
Normal file
333
CLAUDE.md
Normal 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 iwd’s D-Bus API directly
|
||||||
|
|
||||||
|
Provide a simple, fast, and reliable Wi-Fi UI
|
||||||
|
|
||||||
|
Follow Enlightenment UX conventions (gadget + popup)
|
||||||
|
|
||||||
|
1.2 Motivation
|
||||||
|
|
||||||
|
ConnMan is increasingly deprecated or undesired on many systems
|
||||||
|
|
||||||
|
iwd is lightweight, fast, and widely adopted (Arch, Fedora, Debian)
|
||||||
|
|
||||||
|
Enlightenment currently lacks a first-class iwd-based Wi-Fi module
|
||||||
|
|
||||||
|
Users want a native, non-NM, non-ConnMan Wi-Fi solution
|
||||||
|
|
||||||
|
2. Goals & Non-Goals
|
||||||
|
2.1 Goals
|
||||||
|
|
||||||
|
Feature parity with basic econnman Wi-Fi features
|
||||||
|
|
||||||
|
Zero dependency on NetworkManager or ConnMan
|
||||||
|
|
||||||
|
D-Bus only (no shelling out to iwctl)
|
||||||
|
|
||||||
|
Minimal background CPU/memory usage
|
||||||
|
|
||||||
|
Robust behavior across suspend/resume and network changes
|
||||||
|
|
||||||
|
2.2 Non-Goals
|
||||||
|
|
||||||
|
Ethernet management
|
||||||
|
|
||||||
|
VPN management
|
||||||
|
|
||||||
|
Cellular (WWAN) support
|
||||||
|
|
||||||
|
Advanced enterprise Wi-Fi UI (EAP tuning beyond basics)
|
||||||
|
|
||||||
|
3. Target Users
|
||||||
|
|
||||||
|
Enlightenment desktop users
|
||||||
|
|
||||||
|
Minimalist / embedded systems using iwd
|
||||||
|
|
||||||
|
Power users avoiding NetworkManager
|
||||||
|
|
||||||
|
Distributions shipping iwd by default
|
||||||
|
|
||||||
|
4. User Experience
|
||||||
|
4.1 Gadget (Shelf Icon)
|
||||||
|
|
||||||
|
Status icon:
|
||||||
|
|
||||||
|
Disconnected
|
||||||
|
|
||||||
|
Connecting
|
||||||
|
|
||||||
|
Connected (signal strength tiers)
|
||||||
|
|
||||||
|
Error
|
||||||
|
|
||||||
|
Tooltip:
|
||||||
|
|
||||||
|
Current SSID
|
||||||
|
|
||||||
|
Signal strength
|
||||||
|
|
||||||
|
Security type
|
||||||
|
|
||||||
|
4.2 Popup UI
|
||||||
|
|
||||||
|
Triggered by clicking the gadget.
|
||||||
|
Sections:
|
||||||
|
|
||||||
|
Current Connection
|
||||||
|
|
||||||
|
SSID
|
||||||
|
|
||||||
|
Signal strength
|
||||||
|
|
||||||
|
IP (optional)
|
||||||
|
|
||||||
|
Disconnect button
|
||||||
|
|
||||||
|
Available Networks
|
||||||
|
|
||||||
|
Sorted by:
|
||||||
|
|
||||||
|
Known networks
|
||||||
|
|
||||||
|
Signal strength
|
||||||
|
|
||||||
|
Icons for:
|
||||||
|
|
||||||
|
Open
|
||||||
|
|
||||||
|
WPA2/WPA3
|
||||||
|
|
||||||
|
Connect button per network
|
||||||
|
|
||||||
|
Actions
|
||||||
|
|
||||||
|
Rescan
|
||||||
|
|
||||||
|
Enable / Disable Wi-Fi
|
||||||
|
|
||||||
|
4.3 Authentication Flow
|
||||||
|
|
||||||
|
On selecting a secured network:
|
||||||
|
|
||||||
|
Prompt for passphrase
|
||||||
|
|
||||||
|
Optional “remember network”
|
||||||
|
|
||||||
|
Errors clearly reported (wrong password, auth failed, etc.)
|
||||||
|
|
||||||
|
5. Functional Requirements
|
||||||
|
5.1 Wi-Fi Control
|
||||||
|
|
||||||
|
Enable / disable Wi-Fi (via iwd Powered)
|
||||||
|
|
||||||
|
Trigger scan
|
||||||
|
|
||||||
|
List available networks
|
||||||
|
|
||||||
|
Connect to a network
|
||||||
|
|
||||||
|
Disconnect from current network
|
||||||
|
|
||||||
|
5.2 Network State Monitoring
|
||||||
|
|
||||||
|
React to:
|
||||||
|
|
||||||
|
Connection changes
|
||||||
|
|
||||||
|
Signal strength changes
|
||||||
|
|
||||||
|
Device availability
|
||||||
|
|
||||||
|
iwd daemon restart
|
||||||
|
|
||||||
|
5.3 Known Networks
|
||||||
|
|
||||||
|
List known (previously connected) networks
|
||||||
|
|
||||||
|
Auto-connect indication
|
||||||
|
|
||||||
|
Forget network
|
||||||
|
|
||||||
|
5.4 Error Handling
|
||||||
|
|
||||||
|
iwd not running
|
||||||
|
|
||||||
|
No wireless device
|
||||||
|
|
||||||
|
Permission denied (polkit)
|
||||||
|
|
||||||
|
Authentication failure
|
||||||
|
|
||||||
|
6. Technical Requirements
|
||||||
|
6.1 Backend
|
||||||
|
|
||||||
|
iwd via D-Bus
|
||||||
|
|
||||||
|
Service: net.connman.iwd
|
||||||
|
|
||||||
|
No external command execution
|
||||||
|
|
||||||
|
6.2 D-Bus Interfaces Used (Non-Exhaustive)
|
||||||
|
|
||||||
|
net.connman.iwd.Adapter
|
||||||
|
|
||||||
|
net.connman.iwd.Device
|
||||||
|
|
||||||
|
net.connman.iwd.Network
|
||||||
|
|
||||||
|
net.connman.iwd.Station
|
||||||
|
|
||||||
|
net.connman.iwd.KnownNetwork
|
||||||
|
|
||||||
|
6.3 Permissions
|
||||||
|
|
||||||
|
Requires polkit rules for:
|
||||||
|
|
||||||
|
Scanning
|
||||||
|
|
||||||
|
Connecting
|
||||||
|
|
||||||
|
Forgetting networks
|
||||||
|
|
||||||
|
Module must gracefully degrade without permissions
|
||||||
|
|
||||||
|
7. Architecture
|
||||||
|
7.1 Module Structure
|
||||||
|
|
||||||
|
e_iwd/
|
||||||
|
├── e_mod_main.c
|
||||||
|
├── e_mod_config.c
|
||||||
|
├── e_mod_gadget.c
|
||||||
|
├── e_mod_popup.c
|
||||||
|
├── iwd/
|
||||||
|
│ ├── iwd_manager.c
|
||||||
|
│ ├── iwd_device.c
|
||||||
|
│ ├── iwd_network.c
|
||||||
|
│ └── iwd_dbus.c
|
||||||
|
└── ui/
|
||||||
|
├── wifi_list.c
|
||||||
|
├── wifi_auth.c
|
||||||
|
└── wifi_status.c
|
||||||
|
|
||||||
|
7.2 Data Flow
|
||||||
|
|
||||||
|
iwd (D-Bus)
|
||||||
|
↓
|
||||||
|
iwd_dbus.c
|
||||||
|
↓
|
||||||
|
iwd_manager / device / network
|
||||||
|
↓
|
||||||
|
UI layer (EFL widgets)
|
||||||
|
↓
|
||||||
|
Gadget / Popup
|
||||||
|
|
||||||
|
7.3 Threading Model
|
||||||
|
|
||||||
|
Single main loop
|
||||||
|
|
||||||
|
Async D-Bus calls via EFL
|
||||||
|
|
||||||
|
No blocking calls on UI thread
|
||||||
|
|
||||||
|
8. State Model
|
||||||
|
8.1 Connection States
|
||||||
|
|
||||||
|
OFF
|
||||||
|
|
||||||
|
IDLE
|
||||||
|
|
||||||
|
SCANNING
|
||||||
|
|
||||||
|
CONNECTING
|
||||||
|
|
||||||
|
CONNECTED
|
||||||
|
|
||||||
|
ERROR
|
||||||
|
|
||||||
|
8.2 Transitions
|
||||||
|
|
||||||
|
Triggered by:
|
||||||
|
|
||||||
|
User actions
|
||||||
|
|
||||||
|
iwd signals
|
||||||
|
|
||||||
|
System suspend/resume
|
||||||
|
|
||||||
|
9. Configuration
|
||||||
|
9.1 Module Settings
|
||||||
|
|
||||||
|
Auto-connect enabled / disabled
|
||||||
|
|
||||||
|
Show hidden networks
|
||||||
|
|
||||||
|
Signal strength refresh interval
|
||||||
|
|
||||||
|
Preferred adapter (if multiple)
|
||||||
|
|
||||||
|
Stored using Enlightenment module config system.
|
||||||
|
10. Performance & Reliability
|
||||||
|
10.1 Performance
|
||||||
|
|
||||||
|
Startup time < 100 ms
|
||||||
|
|
||||||
|
No periodic polling; signal-driven updates
|
||||||
|
|
||||||
|
Minimal memory footprint (< 5 MB)
|
||||||
|
|
||||||
|
10.2 Reliability
|
||||||
|
|
||||||
|
Handle iwd restart gracefully
|
||||||
|
|
||||||
|
Auto-rebind D-Bus objects
|
||||||
|
|
||||||
|
Avoid crashes on device hot-plug
|
||||||
|
|
||||||
|
11. Security Considerations
|
||||||
|
|
||||||
|
Never log passphrases
|
||||||
|
|
||||||
|
Passphrases only sent over D-Bus to iwd
|
||||||
|
|
||||||
|
Respect system polkit policies
|
||||||
|
|
||||||
|
No plaintext storage in module config
|
||||||
|
|
||||||
|
|
||||||
|
13. Success Metrics
|
||||||
|
|
||||||
|
Successful connect/disconnect in ≥ 99% cases
|
||||||
|
|
||||||
|
No UI freezes during scan/connect
|
||||||
|
|
||||||
|
Parity with econnman Wi-Fi UX
|
||||||
|
|
||||||
|
Adoption by at least one major distro Enlightenment spin
|
||||||
|
|
||||||
|
14. Future Extensions (Out of Scope)
|
||||||
|
|
||||||
|
Ethernet support
|
||||||
|
|
||||||
|
VPN integration
|
||||||
|
|
||||||
|
QR-based Wi-Fi sharing
|
||||||
|
|
||||||
|
Per-network advanced EAP UI
|
||||||
|
|
||||||
|
15. Open Questions / Risks
|
||||||
|
|
||||||
|
Polkit UX integration (password prompts)
|
||||||
|
|
||||||
|
Multiple adapter handling UX
|
||||||
|
|
||||||
|
iwd API changes across versions
|
||||||
442
CONTRIBUTING.md
Normal file
442
CONTRIBUTING.md
Normal 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
490
INSTALL.md
Normal 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
47
LICENSE
|
|
@ -1,33 +1,24 @@
|
||||||
|
BSD 2-Clause License
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Copyright (c) 2025, eiwd contributors
|
||||||
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:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
Redistribution and use in source and binary forms, with or without
|
||||||
all copies of the Software and its Copyright notices. In addition publicly
|
modification, are permitted provided that the following conditions are met:
|
||||||
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.
|
|
||||||
|
|
||||||
Please see the COPYING-PLAIN for a plain-english explanation of this notice
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
and its intent.
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
this list of conditions and the following disclaimer in the documentation
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
and/or other materials provided with the distribution.
|
||||||
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
||||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
|
||||||
421
PLAN.md
Normal file
421
PLAN.md
Normal 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
316
README.md
|
|
@ -1,161 +1,225 @@
|
||||||
# e_iwd — Enlightenment Wi-Fi module (iwd backend)
|
# eiwd - Enlightenment Wi-Fi Module (iwd Backend)
|
||||||
|
|
||||||
A native [Enlightenment](https://www.enlightenment.org/) gadget that
|
A native Enlightenment module for managing Wi-Fi connections using Intel Wireless Daemon (iwd) as the backend.
|
||||||
manages wireless connections through [iwd](https://iwd.wiki.kernel.org/),
|
|
||||||
the Intel Wireless Daemon. No NetworkManager, no ConnMan, no shelling
|
|
||||||
out to `iwctl` — everything goes over iwd's D‑Bus API.
|
|
||||||
|
|
||||||
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
|
## Features
|
||||||
|
|
||||||
- **Shelf gadget** with a signal-tier icon (off / acquiring / weak…excellent)
|
### Core Functionality
|
||||||
and a tooltip showing the current SSID, security type, and signal level.
|
- **Device Management**: Automatic detection of wireless adapters
|
||||||
- **Popup network browser** (left-click the gadget):
|
- **Network Discovery**: Fast scanning with signal strength indicators
|
||||||
- status line: disabled / disconnected / scanning / connecting / connected
|
- **Connection Management**: Connect/disconnect with one click
|
||||||
- sorted network list — connected first, then known networks, then by
|
- **Known Networks**: Automatic connection to saved networks
|
||||||
signal strength; long SSIDs are truncated to keep the popup tidy
|
- **Hidden Networks**: Support for connecting to non-broadcast SSIDs
|
||||||
- per-row signal bars and security tag (`open` / `WPA` / `WEP` / `802.1X`)
|
- **Security**: WPA2/WPA3-PSK authentication with secure passphrase handling
|
||||||
- **Connect** by clicking a row, **Forget** (`✕`) on known networks
|
|
||||||
- **Rescan**, **Enable / Disable** Wi‑Fi
|
|
||||||
- **Disconnect** button visible while connected
|
|
||||||
- **Hidden…** button to join a non-broadcasting SSID
|
|
||||||
- **Authentication agent** registered with iwd:
|
|
||||||
- passphrase prompt for new protected networks (modal dialog window)
|
|
||||||
- cancel-on-`Agent.Cancel` so iwd-initiated cancellations close the
|
|
||||||
open prompt cleanly
|
|
||||||
- polite stubs for `RequestUserNameAndPassword`, `RequestUserPassword`
|
|
||||||
and `RequestPrivateKeyPassphrase` so iwd doesn't unregister us when
|
|
||||||
it tries them on EAP networks
|
|
||||||
- **Settings dialog** (right-click the gadget → Settings):
|
|
||||||
- auto-connect to known networks
|
|
||||||
- show hidden networks
|
|
||||||
- signal refresh interval
|
|
||||||
- preferred wireless adapter
|
|
||||||
- **Robust to iwd lifecycle**: tracks `net.connman.iwd` name owner,
|
|
||||||
re-binds objects on restart, clears state on departure.
|
|
||||||
|
|
||||||
## Architecture
|
### User Interface
|
||||||
|
- **Shelf Gadget**: Compact icon showing connection status
|
||||||
|
- **Visual Feedback**: Color-coded states (disconnected, connecting, connected, error)
|
||||||
|
- **Popup Menu**: Quick access to available networks and actions
|
||||||
|
- **Configuration Dialog**: Customizable settings and preferences
|
||||||
|
- **Theme Support**: Full Edje theme integration with signal strength display
|
||||||
|
|
||||||
```
|
### Advanced Features
|
||||||
e_iwd/
|
- **Daemon Recovery**: Automatic reconnection when iwd restarts
|
||||||
├── src/
|
- **Multi-Adapter Support**: Detection and management of multiple wireless devices
|
||||||
│ ├── e_mod_main.c module init/shutdown
|
- **State Machine**: Robust connection state tracking
|
||||||
│ ├── e_mod_gadget.c gadcon provider, icon + tooltip + menu
|
- **Error Handling**: User-friendly error messages with troubleshooting hints
|
||||||
│ ├── e_mod_popup.c network list popup
|
- **Polkit Integration**: Respects system authorization policies
|
||||||
│ ├── 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
|
|
||||||
```
|
|
||||||
|
|
||||||
Data flow:
|
## Requirements
|
||||||
|
|
||||||
```
|
### Build Dependencies
|
||||||
iwd (D-Bus) ──► iwd_dbus ──► iwd_manager ──► iwd_device / iwd_network
|
- `enlightenment` >= 0.25
|
||||||
│
|
- `efl` (Elementary, Eldbus, Ecore, Evas, Edje, Eina) >= 1.26
|
||||||
├──► listeners (gadget, popup)
|
- `meson` >= 0.56
|
||||||
└──► Iwd_Agent ──► UI passphrase prompt
|
- `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
|
### Runtime Dependencies
|
||||||
its widgets. Everything is async — no blocking calls on the UI thread.
|
- `enlightenment` >= 0.25
|
||||||
|
- `efl` runtime libraries
|
||||||
|
- `iwd` >= 1.0
|
||||||
|
- `dbus` system bus
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
- `polkit` for fine-grained permission management
|
||||||
|
- `gettext` for internationalization support
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
Dependencies (development headers):
|
```bash
|
||||||
|
# Clone repository
|
||||||
|
git clone <repository-url> eiwd
|
||||||
|
cd eiwd
|
||||||
|
|
||||||
- Enlightenment ≥ 0.25 (tested against 0.27)
|
# Configure build
|
||||||
- EFL ≥ 1.26 (Eldbus, Elementary, Edje, Ecore, Eina)
|
|
||||||
- meson + ninja
|
|
||||||
- a running `iwd` ≥ 1.0 (runtime, not build-time)
|
|
||||||
|
|
||||||
Build and install:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
meson setup build
|
meson setup build
|
||||||
|
|
||||||
|
# Compile
|
||||||
ninja -C build
|
ninja -C build
|
||||||
|
|
||||||
|
# Install (as root)
|
||||||
sudo ninja -C build install
|
sudo ninja -C build install
|
||||||
```
|
```
|
||||||
|
|
||||||
The module is installed to
|
### Build Options
|
||||||
`<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.
|
|
||||||
|
|
||||||
Once installed, enable it from **Settings → Modules → Extensions →
|
```bash
|
||||||
iwd**, then add the gadget to a shelf or the desktop via
|
# Disable internationalization
|
||||||
**Settings → Gadgets**.
|
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`).
|
## Installation
|
||||||
- Your user must be allowed to talk to `net.connman.iwd` on the system
|
|
||||||
bus. On most distros this means being in the `network` group, or
|
|
||||||
having a polkit rule for the `net.connman.iwd` interfaces. The module
|
|
||||||
degrades gracefully when permissions are missing — you'll just see an
|
|
||||||
empty list.
|
|
||||||
- A wireless adapter managed by iwd (i.e. not claimed by
|
|
||||||
NetworkManager / wpa_supplicant).
|
|
||||||
|
|
||||||
## Usage
|
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 |
|
After installation:
|
||||||
|---|---|
|
1. Open Enlightenment Settings → Modules
|
||||||
| Open the network list | Left-click the gadget |
|
2. Find "IWD Wi-Fi" in the list
|
||||||
| Open settings | Right-click the gadget → Settings |
|
3. Click "Load" to enable the module
|
||||||
| Connect to a known network | Click its row in the list |
|
4. Add the gadget to your shelf via shelf settings
|
||||||
| Connect to a new protected network | Click its row, enter the passphrase in the dialog |
|
|
||||||
| Forget a known network | Click the `✕` button on its row |
|
|
||||||
| Disconnect | Click **Disconnect** in the popup (visible while connected) |
|
|
||||||
| Join a hidden SSID | Click **Hidden…**, enter SSID and (optional) passphrase |
|
|
||||||
| Rescan | Click **Rescan** |
|
|
||||||
| Disable / enable Wi-Fi | Click **Disable** / **Enable** in the popup |
|
|
||||||
|
|
||||||
Passphrases are sent straight to iwd over D-Bus. They are never logged,
|
|
||||||
never written to the module config, and are zeroed in memory once the
|
|
||||||
dialog closes.
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Settings are persisted via Enlightenment's standard config system as
|
### Module Settings
|
||||||
`module.iwd` (an `eet` file under your E config profile, e.g.
|
|
||||||
`~/.e/e/config/<profile>/module.iwd.cfg`). Fields:
|
|
||||||
|
|
||||||
| Field | Default | Meaning |
|
Access via right-click on the gadget or Settings → Modules → IWD → Configure:
|
||||||
|---|---|---|
|
|
||||||
| `auto_connect` | on | Let iwd auto-connect to known networks |
|
|
||||||
| `show_hidden` | off | Reveal hidden networks in the list |
|
|
||||||
| `refresh_interval` | 5 | Signal-strength refresh interval (seconds) |
|
|
||||||
| `preferred_adapter` | — | Preferred wireless adapter (blank = auto) |
|
|
||||||
|
|
||||||
## Status
|
- **Auto-connect**: Automatically connect to known networks
|
||||||
|
- **Show hidden networks**: Display option to connect to hidden SSIDs
|
||||||
|
- **Signal refresh interval**: Update frequency (1-60 seconds)
|
||||||
|
- **Preferred adapter**: Select default wireless device (multi-adapter systems)
|
||||||
|
|
||||||
Phases 0–4 of the project are complete and the module builds cleanly
|
### iwd Setup
|
||||||
against EFL 1.28 / Enlightenment 0.27. Phase 5 (robustness) and Phase 6
|
|
||||||
(packaging) are partially landed.
|
|
||||||
|
|
||||||
Known gaps:
|
Ensure iwd is running and enabled:
|
||||||
|
|
||||||
- No custom theme `edj` — the gadget uses freedesktop icon names from
|
```bash
|
||||||
the active icon theme.
|
# Enable and start iwd
|
||||||
- No suspend / resume integration.
|
sudo systemctl enable --now iwd
|
||||||
- No EAP UI (RequestUserName / RequestPrivateKey are stubbed to
|
|
||||||
refuse).
|
# Check status
|
||||||
- Multi-adapter UX is auto-select-first; the preferred-adapter setting
|
sudo systemctl status iwd
|
||||||
is plumbed but not yet honored by the manager.
|
```
|
||||||
- Not yet tested by valgrind for leaks.
|
|
||||||
|
### 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
|
## 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
448
TESTING.md
Normal 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
|
||||||
|
```
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,18 @@
|
||||||
install_data('module.desktop', install_dir : module_dir)
|
# Install desktop file
|
||||||
|
install_data('module.desktop',
|
||||||
edje_cc = find_program('edje_cc')
|
install_dir: dir_module
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Link
|
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
|
Icon=e-module-iwd
|
||||||
Comment=Wi-Fi management via iwd
|
X-Enlightenment-ModuleType=system
|
||||||
X-Enlightenment-ModuleType=utils
|
|
||||||
|
|
|
||||||
188
data/theme.edc
Normal file
188
data/theme.edc
Normal 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 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
e_iwd.spec
40
e_iwd.spec
|
|
@ -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.
|
|
||||||
84
meson.build
84
meson.build
|
|
@ -1,21 +1,77 @@
|
||||||
project('e_iwd', 'c',
|
project('e-iwd', 'c',
|
||||||
version : '0.1.0',
|
version: '0.1.0',
|
||||||
license : 'MIT',
|
default_options: ['c_std=c11', 'warning_level=2'],
|
||||||
default_options : ['c_std=gnu99', 'warning_level=2'])
|
meson_version: '>= 0.56.0'
|
||||||
|
)
|
||||||
|
|
||||||
cc = meson.get_compiler('c')
|
# Dependencies
|
||||||
|
|
||||||
eldbus = dependency('eldbus')
|
|
||||||
elementary = dependency('elementary')
|
|
||||||
enlightenment = dependency('enlightenment')
|
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',
|
# Get Enlightenment version and module architecture
|
||||||
default_value: 'linux-gnu-@0@'.format(host_machine.cpu()))
|
e_version = enlightenment.version()
|
||||||
module_dir = join_paths(get_option('libdir'), 'enlightenment', 'modules', 'iwd')
|
|
||||||
|
|
||||||
add_project_arguments('-DPACKAGE="e_iwd"',
|
# Detect system ABI (gnu, musl, etc.)
|
||||||
'-DPACKAGE_VERSION="@0@"'.format(meson.project_version()),
|
cc = meson.get_compiler('c')
|
||||||
language : '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('src')
|
||||||
subdir('data')
|
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
3
meson_options.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Build options
|
||||||
|
option('nls', type: 'boolean', value: true,
|
||||||
|
description: 'Enable internationalization support')
|
||||||
2
metadata/layout.conf
Normal file
2
metadata/layout.conf
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
masters = gentoo
|
||||||
|
repo-name = x-eiwd
|
||||||
361
packaging/README.md
Normal file
361
packaging/README.md
Normal 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
51
packaging/arch/PKGBUILD
Normal 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
73
packaging/create-release.sh
Executable 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"
|
||||||
56
packaging/gentoo/eiwd-0.1.0.ebuild
Normal file
56
packaging/gentoo/eiwd-0.1.0.ebuild
Normal 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."
|
||||||
|
}
|
||||||
1
packaging/profiles/repo_name
Normal file
1
packaging/profiles/repo_name
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
x-eiwd
|
||||||
10
po/POTFILES.in
Normal file
10
po/POTFILES.in
Normal 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
7
po/meson.build
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# i18n support
|
||||||
|
i18n.gettext('eiwd',
|
||||||
|
args: [
|
||||||
|
'--directory=' + meson.source_root(),
|
||||||
|
'--from-code=UTF-8',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
@ -1,146 +1,162 @@
|
||||||
#include "e_mod_main.h"
|
#include "e_mod_main.h"
|
||||||
#include "e_mod_config.h"
|
|
||||||
#include <e_config_data.h>
|
|
||||||
|
|
||||||
#define CONFIG_DOMAIN "module.iwd"
|
/* Configuration dialog structure */
|
||||||
#define CONFIG_VERSION 1
|
typedef struct _E_Config_Dialog_Data
|
||||||
|
|
||||||
E_Iwd_Config *e_iwd_config = NULL;
|
|
||||||
static E_Config_DD *_edd = NULL;
|
|
||||||
|
|
||||||
static void
|
|
||||||
_edd_setup(void)
|
|
||||||
{
|
{
|
||||||
if (_edd) return;
|
int auto_connect;
|
||||||
_edd = E_CONFIG_DD_NEW("E_Iwd_Config", E_Iwd_Config);
|
int show_hidden_networks;
|
||||||
E_CONFIG_VAL(_edd, E_Iwd_Config, version, INT);
|
int signal_refresh_interval;
|
||||||
E_CONFIG_VAL(_edd, E_Iwd_Config, auto_connect, INT);
|
|
||||||
E_CONFIG_VAL(_edd, E_Iwd_Config, show_hidden, INT);
|
|
||||||
E_CONFIG_VAL(_edd, E_Iwd_Config, refresh_interval, INT);
|
|
||||||
E_CONFIG_VAL(_edd, E_Iwd_Config, preferred_adapter, STR);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
e_iwd_config_load(void)
|
|
||||||
{
|
|
||||||
_edd_setup();
|
|
||||||
e_iwd_config = e_config_domain_load(CONFIG_DOMAIN, _edd);
|
|
||||||
if (e_iwd_config && e_iwd_config->version == CONFIG_VERSION) return;
|
|
||||||
|
|
||||||
/* Missing or out-of-date — start fresh with defaults. */
|
|
||||||
if (e_iwd_config)
|
|
||||||
{
|
|
||||||
if (e_iwd_config->preferred_adapter)
|
|
||||||
eina_stringshare_del(e_iwd_config->preferred_adapter);
|
|
||||||
free(e_iwd_config);
|
|
||||||
}
|
|
||||||
e_iwd_config = E_NEW(E_Iwd_Config, 1);
|
|
||||||
e_iwd_config->version = CONFIG_VERSION;
|
|
||||||
e_iwd_config->auto_connect = 1;
|
|
||||||
e_iwd_config->show_hidden = 0;
|
|
||||||
e_iwd_config->refresh_interval = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
e_iwd_config_save(void)
|
|
||||||
{
|
|
||||||
if (!_edd || !e_iwd_config) return;
|
|
||||||
e_config_domain_save(CONFIG_DOMAIN, _edd, e_iwd_config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----- Settings dialog ------------------------------------------------ */
|
|
||||||
|
|
||||||
struct _E_Config_Dialog_Data
|
|
||||||
{
|
|
||||||
int auto_connect;
|
|
||||||
int show_hidden;
|
|
||||||
int refresh_interval;
|
|
||||||
char *preferred_adapter;
|
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 *
|
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 *cfdata;
|
||||||
E_Config_Dialog_Data *c = E_NEW(E_Config_Dialog_Data, 1);
|
|
||||||
c->auto_connect = e_iwd_config->auto_connect;
|
if (!iwd_mod || !iwd_mod->conf) return NULL;
|
||||||
c->show_hidden = e_iwd_config->show_hidden;
|
|
||||||
c->refresh_interval = e_iwd_config->refresh_interval;
|
cfdata = E_NEW(E_Config_Dialog_Data, 1);
|
||||||
c->preferred_adapter = e_iwd_config->preferred_adapter
|
if (!cfdata) return NULL;
|
||||||
? strdup(e_iwd_config->preferred_adapter) : strdup("");
|
|
||||||
return c;
|
/* 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
|
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;
|
if (!cfdata) return;
|
||||||
free(c->preferred_adapter);
|
|
||||||
E_FREE(c);
|
if (cfdata->preferred_adapter)
|
||||||
|
free(cfdata->preferred_adapter);
|
||||||
|
|
||||||
|
E_FREE(cfdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Create basic UI */
|
||||||
static Evas_Object *
|
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;
|
Evas_Object *o, *of, *ob;
|
||||||
|
|
||||||
o = e_widget_list_add(evas, 0, 0);
|
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",
|
ob = e_widget_check_add(evas, "Auto-connect to known networks",
|
||||||
&c->auto_connect);
|
&(cfdata->auto_connect));
|
||||||
e_widget_framelist_object_append(of, ob);
|
e_widget_framelist_object_append(of, ob);
|
||||||
|
|
||||||
ob = e_widget_check_add(evas, "Show hidden networks",
|
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_framelist_object_append(of, ob);
|
||||||
|
|
||||||
e_widget_list_object_append(o, of, 1, 1, 0.5);
|
e_widget_list_object_append(o, of, 1, 1, 0.5);
|
||||||
|
|
||||||
|
/* Performance settings frame */
|
||||||
of = e_widget_framelist_add(evas, "Performance", 0);
|
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);
|
e_widget_framelist_object_append(of, ob);
|
||||||
|
|
||||||
ob = e_widget_slider_add(evas, 1, 0, "%1.0f", 1.0, 60.0, 1.0, 0,
|
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_framelist_object_append(of, ob);
|
||||||
|
|
||||||
e_widget_list_object_append(o, of, 1, 1, 0.5);
|
e_widget_list_object_append(o, of, 1, 1, 0.5);
|
||||||
|
|
||||||
of = e_widget_framelist_add(evas, "Adapter", 0);
|
/* Adapter settings frame (if multiple adapters available) */
|
||||||
ob = e_widget_label_add(evas, "Preferred wireless adapter (blank = auto):");
|
Eina_List *devices = iwd_devices_get();
|
||||||
e_widget_framelist_object_append(of, ob);
|
if (eina_list_count(devices) > 1)
|
||||||
ob = e_widget_entry_add(evas, &c->preferred_adapter, NULL, NULL, NULL);
|
{
|
||||||
e_widget_framelist_object_append(of, ob);
|
of = e_widget_framelist_add(evas, "Adapter Selection", 0);
|
||||||
e_widget_list_object_append(o, of, 1, 1, 0.5);
|
|
||||||
|
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;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Apply configuration */
|
||||||
static int
|
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;
|
if (!iwd_mod || !iwd_mod->conf) return 0;
|
||||||
e_iwd_config->auto_connect = c->auto_connect;
|
|
||||||
e_iwd_config->show_hidden = c->show_hidden;
|
/* Update config */
|
||||||
e_iwd_config->refresh_interval = c->refresh_interval;
|
iwd_mod->conf->auto_connect = cfdata->auto_connect;
|
||||||
if (e_iwd_config->preferred_adapter)
|
iwd_mod->conf->show_hidden_networks = cfdata->show_hidden_networks;
|
||||||
eina_stringshare_del(e_iwd_config->preferred_adapter);
|
iwd_mod->conf->signal_refresh_interval = cfdata->signal_refresh_interval;
|
||||||
e_iwd_config->preferred_adapter =
|
|
||||||
(c->preferred_adapter && *c->preferred_adapter)
|
if (cfdata->preferred_adapter)
|
||||||
? eina_stringshare_add(c->preferred_adapter) : NULL;
|
{
|
||||||
e_iwd_config_save();
|
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;
|
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);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,298 +1,368 @@
|
||||||
#include "e_mod_main.h"
|
#include "e_mod_main.h"
|
||||||
#include "e_mod_gadget.h"
|
#include <limits.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>
|
|
||||||
|
|
||||||
/* ----- 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;
|
GADCON_CLIENT_CLASS_VERSION,
|
||||||
Evas_Object *o_base; /* themed edje, gcc->o_base */
|
"iwd",
|
||||||
Evas_Object *o_icon; /* swallowed into o_base */
|
{
|
||||||
} Instance;
|
_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 ----------------------------------------------------- */
|
/* Initialize gadget subsystem */
|
||||||
|
void
|
||||||
/* Walk the manager state to find the network we're currently connected to,
|
e_iwd_gadget_init(void)
|
||||||
* if any. Used both for the signal-tier icon and for the tooltip. */
|
|
||||||
static Iwd_Network *
|
|
||||||
_active_network(void)
|
|
||||||
{
|
{
|
||||||
if (!e_iwd || !e_iwd->manager) return NULL;
|
DBG("Initializing gadget");
|
||||||
const Eina_Hash *devs = iwd_manager_devices(e_iwd->manager);
|
|
||||||
const Eina_Hash *nets = iwd_manager_networks(e_iwd->manager);
|
gadcon_class = (E_Gadcon_Client_Class *)&_gc_class;
|
||||||
if (!devs || !nets) return NULL;
|
e_gadcon_provider_register(gadcon_class);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *
|
/* Shutdown gadget subsystem */
|
||||||
_icon_for_signal_tier(int tier)
|
void
|
||||||
|
e_iwd_gadget_shutdown(void)
|
||||||
{
|
{
|
||||||
switch (tier)
|
DBG("Shutting down gadget");
|
||||||
{
|
|
||||||
case 4: return "network-wireless-signal-excellent";
|
if (gadcon_class)
|
||||||
case 3: return "network-wireless-signal-good";
|
{
|
||||||
case 2: return "network-wireless-signal-ok";
|
e_gadcon_provider_unregister(gadcon_class);
|
||||||
case 1: return "network-wireless-signal-weak";
|
gadcon_class = NULL;
|
||||||
default: return "network-wireless-signal-none";
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *
|
/* Gadcon init */
|
||||||
_icon_name_for_state(Iwd_State s)
|
|
||||||
{
|
|
||||||
switch (s)
|
|
||||||
{
|
|
||||||
case IWD_STATE_OFF: return "network-offline";
|
|
||||||
case IWD_STATE_IDLE: return "network-wireless-disconnected";
|
|
||||||
case IWD_STATE_SCANNING: return "network-wireless-acquiring";
|
|
||||||
case IWD_STATE_CONNECTING: return "network-wireless-acquiring";
|
|
||||||
case IWD_STATE_CONNECTED:
|
|
||||||
{
|
|
||||||
Iwd_Network *n = _active_network();
|
|
||||||
return _icon_for_signal_tier(n ? iwd_network_signal_tier(n) : 0);
|
|
||||||
}
|
|
||||||
case IWD_STATE_ERROR: return "network-error";
|
|
||||||
}
|
|
||||||
return "network-wireless";
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *
|
|
||||||
_state_label(Iwd_State s)
|
|
||||||
{
|
|
||||||
switch (s)
|
|
||||||
{
|
|
||||||
case IWD_STATE_OFF: return "Wi-Fi disabled";
|
|
||||||
case IWD_STATE_IDLE: return "Disconnected";
|
|
||||||
case IWD_STATE_SCANNING: return "Scanning";
|
|
||||||
case IWD_STATE_CONNECTING: return "Connecting";
|
|
||||||
case IWD_STATE_CONNECTED: return "Connected";
|
|
||||||
case IWD_STATE_ERROR: return "Error";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *
|
|
||||||
_sec_label(int s)
|
|
||||||
{
|
|
||||||
/* Iwd_Security values, kept in sync with iwd_network.h. */
|
|
||||||
switch (s) { case 0: return "open"; case 1: return "WPA";
|
|
||||||
case 2: return "802.1X"; case 3: return "WEP"; }
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_build_tooltip(Instance *inst, Iwd_State s)
|
|
||||||
{
|
|
||||||
char buf[256];
|
|
||||||
if (s == IWD_STATE_CONNECTED)
|
|
||||||
{
|
|
||||||
Iwd_Network *n = _active_network();
|
|
||||||
if (n)
|
|
||||||
snprintf(buf, sizeof(buf), "Wi-Fi: %s — %s — signal %d/4",
|
|
||||||
n->ssid ? n->ssid : "?",
|
|
||||||
_sec_label(n->security),
|
|
||||||
iwd_network_signal_tier(n));
|
|
||||||
else
|
|
||||||
snprintf(buf, sizeof(buf), "Wi-Fi: connected");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
snprintf(buf, sizeof(buf), "Wi-Fi: %s", _state_label(s));
|
|
||||||
elm_object_tooltip_text_set(inst->o_base, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_inst_refresh(Instance *inst)
|
|
||||||
{
|
|
||||||
if (!inst || !inst->o_icon || !e_iwd) return;
|
|
||||||
Iwd_State s = iwd_manager_state(e_iwd->manager);
|
|
||||||
e_icon_fdo_icon_set(inst->o_icon, _icon_name_for_state(s));
|
|
||||||
_build_tooltip(inst, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Listener invoked by iwd_manager whenever state changes. */
|
|
||||||
static void
|
|
||||||
_on_manager_change(void *data EINA_UNUSED, Iwd_Manager *m EINA_UNUSED)
|
|
||||||
{
|
|
||||||
Eina_List *l;
|
|
||||||
Instance *inst;
|
|
||||||
EINA_LIST_FOREACH(_instances, l, inst) _inst_refresh(inst);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----- click → popup --------------------------------------------------- */
|
|
||||||
|
|
||||||
static void
|
|
||||||
_menu_settings_cb(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
|
|
||||||
{
|
|
||||||
e_iwd_config_dialog_show();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_show_menu(Instance *inst, Evas_Event_Mouse_Down *ev)
|
|
||||||
{
|
|
||||||
E_Zone *zone = e_zone_current_get();
|
|
||||||
E_Menu *m = e_menu_new();
|
|
||||||
E_Menu_Item *mi = e_menu_item_new(m);
|
|
||||||
e_menu_item_label_set(mi, "Settings");
|
|
||||||
e_util_menu_item_theme_icon_set(mi, "preferences-system");
|
|
||||||
e_menu_item_callback_set(mi, _menu_settings_cb, inst);
|
|
||||||
|
|
||||||
int x, y;
|
|
||||||
e_gadcon_canvas_zone_geometry_get(inst->gcc->gadcon, &x, &y, NULL, NULL);
|
|
||||||
e_menu_activate_mouse(m, zone, x + ev->output.x, y + ev->output.y,
|
|
||||||
1, 1, E_MENU_POP_DIRECTION_AUTO, ev->timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_on_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
|
|
||||||
{
|
|
||||||
Evas_Event_Mouse_Down *ev = event_info;
|
|
||||||
Instance *inst = data;
|
|
||||||
if (ev->button == 1)
|
|
||||||
e_iwd_popup_toggle(inst->gcc);
|
|
||||||
else if (ev->button == 3)
|
|
||||||
_show_menu(inst, ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----- helpers --------------------------------------------------------- */
|
|
||||||
|
|
||||||
static char *
|
|
||||||
_theme_path(void)
|
|
||||||
{
|
|
||||||
static char buf[4096];
|
|
||||||
if (!e_iwd || !e_iwd->module) return NULL;
|
|
||||||
snprintf(buf, sizeof(buf), "%s/e-module-iwd.edj",
|
|
||||||
e_module_dir_get(e_iwd->module));
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----- gadcon class ---------------------------------------------------- */
|
|
||||||
|
|
||||||
static E_Gadcon_Client *
|
static E_Gadcon_Client *
|
||||||
_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style)
|
_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style)
|
||||||
{
|
{
|
||||||
Instance *inst = E_NEW(Instance, 1);
|
Instance *inst;
|
||||||
const char *path = _theme_path();
|
E_Gadcon_Client *gcc;
|
||||||
|
Evas_Object *o;
|
||||||
|
|
||||||
/* themed edje is the gadcon o_base — its intrinsic min comes from the
|
DBG("Creating gadget instance");
|
||||||
* 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;
|
|
||||||
|
|
||||||
/* the actual fdo icon goes into the swallow part */
|
inst = E_NEW(Instance, 1);
|
||||||
Evas_Object *icon = e_icon_add(gc->evas);
|
if (!inst) return NULL;
|
||||||
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->gcc = e_gadcon_client_new(gc, name, id, style, base);
|
/* Create edje object */
|
||||||
inst->gcc->data = inst;
|
o = edje_object_add(gc->evas);
|
||||||
|
|
||||||
evas_object_event_callback_add(base, EVAS_CALLBACK_MOUSE_DOWN,
|
/* Load theme */
|
||||||
_on_mouse_down, inst);
|
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);
|
if (!edje_object_file_set(o, theme_path, "e/modules/iwd/main"))
|
||||||
_inst_refresh(inst);
|
{
|
||||||
return inst->gcc;
|
/* 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
|
static void
|
||||||
_gc_shutdown(E_Gadcon_Client *gcc)
|
_gc_shutdown(E_Gadcon_Client *gcc)
|
||||||
{
|
{
|
||||||
Instance *inst = gcc->data;
|
Instance *inst;
|
||||||
if (!inst) return;
|
|
||||||
_instances = eina_list_remove(_instances, inst);
|
DBG("Destroying gadget instance");
|
||||||
if (inst->o_icon) evas_object_del(inst->o_icon);
|
|
||||||
if (inst->o_base) evas_object_del(inst->o_base);
|
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);
|
E_FREE(inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Gadcon orient */
|
||||||
static void
|
static void
|
||||||
_gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient EINA_UNUSED)
|
_gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient EINA_UNUSED)
|
||||||
{
|
{
|
||||||
Instance *inst = gcc->data;
|
Instance *inst;
|
||||||
Evas_Coord mw = 0, mh = 0;
|
Evas_Coord mw, mh;
|
||||||
if (!inst || !inst->o_base) return;
|
|
||||||
edje_object_size_min_get(inst->o_base, &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))
|
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 (mw < 4) mw = 4;
|
||||||
if (mh < 4) mh = 4;
|
if (mh < 4) mh = 4;
|
||||||
e_gadcon_client_aspect_set(gcc, mw, mh);
|
e_gadcon_client_aspect_set(gcc, mw, mh);
|
||||||
e_gadcon_client_min_size_set(gcc, mw, mh);
|
e_gadcon_client_min_size_set(gcc, mw, mh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Gadcon label */
|
||||||
static const char *
|
static const char *
|
||||||
_gc_label(const E_Gadcon_Client_Class *cc EINA_UNUSED) { return "iwd"; }
|
_gc_label(const E_Gadcon_Client_Class *client_class EINA_UNUSED)
|
||||||
|
|
||||||
static Evas_Object *
|
|
||||||
_gc_icon(const E_Gadcon_Client_Class *cc EINA_UNUSED, Evas *evas)
|
|
||||||
{
|
{
|
||||||
const char *path = _theme_path();
|
return "IWD Wi-Fi";
|
||||||
Evas_Object *o = edje_object_add(evas);
|
}
|
||||||
if (path) edje_object_file_set(o, path, "icon");
|
|
||||||
|
/* 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;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Generate new ID */
|
||||||
static const char *
|
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];
|
static char buf[128];
|
||||||
snprintf(buf, sizeof(buf), "%s.%d", cc->name,
|
Mod *mod = iwd_mod;
|
||||||
eina_list_count(_instances) + 1);
|
|
||||||
|
snprintf(buf, sizeof(buf), "%s.%d", client_class->name,
|
||||||
|
mod ? eina_list_count(mod->instances) + 1 : 1);
|
||||||
return buf;
|
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,
|
Instance *inst = data;
|
||||||
"iwd",
|
Evas_Event_Mouse_Down *ev = event_info;
|
||||||
{ _gc_init, _gc_shutdown, _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL, NULL },
|
|
||||||
E_GADCON_CLIENT_STYLE_PLAIN
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ----- public ---------------------------------------------------------- */
|
if (!inst)
|
||||||
|
{
|
||||||
|
e_util_dialog_show("Debug", "Instance is NULL!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
if (ev->button == 1) /* Left click */
|
||||||
e_iwd_gadget_init(void)
|
{
|
||||||
{
|
INF("Gadget clicked - popup=%p device=%p", inst->popup, inst->device);
|
||||||
e_gadcon_provider_register(&_gadcon_class);
|
|
||||||
if (e_iwd && e_iwd->manager)
|
if (inst->popup)
|
||||||
iwd_manager_listener_add(e_iwd->manager, _on_manager_change, NULL);
|
{
|
||||||
|
/* 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
|
/* Update gadget icon and tooltip */
|
||||||
e_iwd_gadget_shutdown(void)
|
static void
|
||||||
|
_gadget_update(Instance *inst)
|
||||||
{
|
{
|
||||||
if (e_iwd && e_iwd->manager)
|
char buf[256];
|
||||||
iwd_manager_listener_del(e_iwd->manager, _on_manager_change, NULL);
|
|
||||||
e_gadcon_provider_unregister(&_gadcon_class);
|
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
|
/* Update timer callback */
|
||||||
e_iwd_gadget_update(void)
|
static Eina_Bool
|
||||||
|
_gadget_update_timer_cb(void *data)
|
||||||
{
|
{
|
||||||
_on_manager_change(NULL, NULL);
|
Instance *inst = data;
|
||||||
|
|
||||||
|
_gadget_update(inst);
|
||||||
|
|
||||||
|
return ECORE_CALLBACK_RENEW;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
239
src/e_mod_main.c
239
src/e_mod_main.c
|
|
@ -1,58 +1,227 @@
|
||||||
#include "e_mod_main.h"
|
#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_modapi_init(E_Module *m)
|
||||||
{
|
{
|
||||||
e_iwd = E_NEW(E_Iwd_Module, 1);
|
Mod *mod;
|
||||||
e_iwd->module = m;
|
|
||||||
|
|
||||||
if (!eldbus_init())
|
/* Initialize logging */
|
||||||
{
|
_e_iwd_log_dom = eina_log_domain_register("e-iwd", EINA_COLOR_CYAN);
|
||||||
E_FREE(e_iwd);
|
if (_e_iwd_log_dom < 0)
|
||||||
return NULL;
|
{
|
||||||
}
|
EINA_LOG_ERR("Could not register log domain 'e-iwd'");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
e_iwd->conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM);
|
INF("IWD Module initializing");
|
||||||
if (!e_iwd->conn)
|
|
||||||
{
|
|
||||||
eldbus_shutdown();
|
|
||||||
E_FREE(e_iwd);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
e_iwd_config_load();
|
/* Allocate module structure */
|
||||||
e_iwd->manager = iwd_manager_new(e_iwd->conn);
|
mod = E_NEW(Mod, 1);
|
||||||
e_iwd_popup_install_passphrase_handler();
|
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();
|
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)
|
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();
|
e_iwd_gadget_shutdown();
|
||||||
if (e_iwd->manager) iwd_manager_free(e_iwd->manager);
|
|
||||||
e_iwd_config_save();
|
/* Shutdown iwd subsystems (must happen before D-Bus shutdown) */
|
||||||
if (e_iwd->conn) eldbus_connection_unref(e_iwd->conn);
|
iwd_network_shutdown();
|
||||||
eldbus_shutdown();
|
iwd_device_shutdown();
|
||||||
E_FREE(e_iwd);
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
EAPI int
|
/* Module save */
|
||||||
|
E_API int
|
||||||
e_modapi_save(E_Module *m EINA_UNUSED)
|
e_modapi_save(E_Module *m EINA_UNUSED)
|
||||||
{
|
{
|
||||||
e_iwd_config_save();
|
Mod *mod = iwd_mod;
|
||||||
return 1;
|
|
||||||
|
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 */
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,97 @@
|
||||||
#define E_MOD_MAIN_H
|
#define E_MOD_MAIN_H
|
||||||
|
|
||||||
#include <e.h>
|
#include <e.h>
|
||||||
|
#include <Eina.h>
|
||||||
#include <Eldbus.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;
|
int config_version;
|
||||||
Eldbus_Connection *conn;
|
Eina_Bool auto_connect;
|
||||||
void *manager; /* Iwd_Manager * */
|
Eina_Bool show_hidden_networks;
|
||||||
void *gadget; /* gadget instance */
|
int signal_refresh_interval;
|
||||||
void *config; /* E_Config_Dialog data */
|
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);
|
/* Global module context */
|
||||||
EAPI int e_modapi_shutdown (E_Module *m);
|
typedef struct _Mod
|
||||||
EAPI int e_modapi_save (E_Module *m);
|
{
|
||||||
|
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
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,445 +1,316 @@
|
||||||
#include "e_mod_main.h"
|
#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 *list, *box, *button, *label;
|
||||||
Evas_Object *box;
|
E_Gadcon_Popup *popup;
|
||||||
Evas_Object *status_lbl;
|
Evas_Coord w, h;
|
||||||
Evas_Object *list;
|
IWD_Network *net;
|
||||||
Evas_Object *btn_scan;
|
|
||||||
Evas_Object *btn_toggle;
|
|
||||||
Evas_Object *btn_hidden;
|
|
||||||
Evas_Object *btn_disconnect; /* shown only when connected */
|
|
||||||
Evas_Object *action_row;
|
|
||||||
Eina_Bool listening;
|
|
||||||
} Popup;
|
|
||||||
|
|
||||||
static Popup *_popup = NULL;
|
|
||||||
|
|
||||||
/* Pending passphrase request from the agent — only one at a time. */
|
|
||||||
static Iwd_Agent_Request *_pending_req = NULL;
|
|
||||||
/* Tracked so iwd's Cancel(reason) can tear down the dialog. */
|
|
||||||
static Evas_Object *_pending_dialog = NULL;
|
|
||||||
/* One-shot passphrase pre-armed by the hidden-network dialog. */
|
|
||||||
static char *_hidden_pending_pass = NULL;
|
|
||||||
|
|
||||||
/* ----- helpers --------------------------------------------------------- */
|
|
||||||
|
|
||||||
static const char *
|
|
||||||
_state_label(Iwd_State s)
|
|
||||||
{
|
|
||||||
switch (s) {
|
|
||||||
case IWD_STATE_OFF: return "Wi-Fi disabled";
|
|
||||||
case IWD_STATE_IDLE: return "Disconnected";
|
|
||||||
case IWD_STATE_SCANNING: return "Scanning…";
|
|
||||||
case IWD_STATE_CONNECTING: return "Connecting…";
|
|
||||||
case IWD_STATE_CONNECTED: return "Connected";
|
|
||||||
case IWD_STATE_ERROR: return "Error";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *
|
|
||||||
_sec_label(Iwd_Security s)
|
|
||||||
{
|
|
||||||
switch (s) {
|
|
||||||
case IWD_SEC_OPEN: return "open";
|
|
||||||
case IWD_SEC_PSK: return "WPA";
|
|
||||||
case IWD_SEC_8021X: return "802.1X";
|
|
||||||
case IWD_SEC_WEP: return "WEP";
|
|
||||||
case IWD_SEC_UNKNOWN: return "?";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
_net_cmp(const void *a, const void *b)
|
|
||||||
{
|
|
||||||
const Iwd_Network *na = a, *nb = b;
|
|
||||||
if (na->connected != nb->connected) return nb->connected - na->connected;
|
|
||||||
if (na->known_path && !nb->known_path) return -1;
|
|
||||||
if (!na->known_path && nb->known_path) return 1;
|
|
||||||
/* Higher signal first within same class. */
|
|
||||||
int ta = iwd_network_signal_tier(na);
|
|
||||||
int tb = iwd_network_signal_tier(nb);
|
|
||||||
if (ta != tb) return tb - ta;
|
|
||||||
if (!na->ssid) return 1;
|
|
||||||
if (!nb->ssid) return -1;
|
|
||||||
return strcasecmp(na->ssid, nb->ssid);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *
|
|
||||||
_signal_bars(int tier)
|
|
||||||
{
|
|
||||||
switch (tier)
|
|
||||||
{
|
|
||||||
case 4: return "▂▄▆█";
|
|
||||||
case 3: return "▂▄▆ ";
|
|
||||||
case 2: return "▂▄ ";
|
|
||||||
case 1: return "▂ ";
|
|
||||||
default: return " ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* First device that has a station; used for "Disconnect" and hidden connect. */
|
|
||||||
static Iwd_Device *
|
|
||||||
_active_device(void)
|
|
||||||
{
|
|
||||||
if (!e_iwd || !e_iwd->manager) return NULL;
|
|
||||||
const Eina_Hash *h = iwd_manager_devices(e_iwd->manager);
|
|
||||||
if (!h) return NULL;
|
|
||||||
Eina_Iterator *it = eina_hash_iterator_data_new((Eina_Hash *)h);
|
|
||||||
Iwd_Device *d, *best = NULL;
|
|
||||||
EINA_ITERATOR_FOREACH(it, d)
|
|
||||||
{
|
|
||||||
if (!d->has_station) continue;
|
|
||||||
if (!best) best = d;
|
|
||||||
if (d->connected_network) { best = d; break; }
|
|
||||||
}
|
|
||||||
eina_iterator_free(it);
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----- list rendering -------------------------------------------------- */
|
|
||||||
|
|
||||||
static void
|
|
||||||
_on_net_clicked(void *data, Evas_Object *obj EINA_UNUSED, void *ev EINA_UNUSED)
|
|
||||||
{
|
|
||||||
Iwd_Network *n = data;
|
|
||||||
if (!n) return;
|
|
||||||
iwd_network_connect(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_on_net_forget(void *data, Evas_Object *obj EINA_UNUSED, void *ev EINA_UNUSED)
|
|
||||||
{
|
|
||||||
Iwd_Network *n = data;
|
|
||||||
if (!n) return;
|
|
||||||
iwd_network_forget(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_rebuild_list(Popup *p)
|
|
||||||
{
|
|
||||||
if (!p->list || !e_iwd || !e_iwd->manager) return;
|
|
||||||
elm_box_clear(p->list);
|
|
||||||
|
|
||||||
/* When the radio is off, hide the (now-stale) network list entirely. */
|
|
||||||
if (iwd_manager_state(e_iwd->manager) == IWD_STATE_OFF) return;
|
|
||||||
|
|
||||||
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
|
|
||||||
if (!h) return;
|
|
||||||
|
|
||||||
/* Snapshot into a list so we can sort. */
|
|
||||||
Eina_List *items = NULL;
|
|
||||||
Eina_Iterator *it = eina_hash_iterator_data_new((Eina_Hash *)h);
|
|
||||||
Iwd_Network *n;
|
|
||||||
EINA_ITERATOR_FOREACH(it, n) items = eina_list_append(items, n);
|
|
||||||
eina_iterator_free(it);
|
|
||||||
items = eina_list_sort(items, eina_list_count(items), _net_cmp);
|
|
||||||
|
|
||||||
Eina_List *l;
|
Eina_List *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);
|
if (!inst)
|
||||||
/* Truncate long SSIDs so the row never forces horizontal scrolling. */
|
{
|
||||||
const char *raw_ssid = n->ssid ? n->ssid : "(hidden)";
|
ERR("iwd_popup_new: inst is NULL");
|
||||||
char ssid_buf[32];
|
return;
|
||||||
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 (n->known_path)
|
if (inst->popup)
|
||||||
{
|
{
|
||||||
Evas_Object *fb = elm_button_add(row);
|
DBG("Popup already exists");
|
||||||
elm_object_text_set(fb, "✕");
|
return;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
elm_box_pack_end(p->list, row);
|
INF("Creating popup for instance %p", inst);
|
||||||
evas_object_show(row);
|
|
||||||
}
|
|
||||||
eina_list_free(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
/* Create popup */
|
||||||
_refresh(Popup *p)
|
popup = e_gadcon_popup_new(inst->gcc, 0);
|
||||||
{
|
if (!popup)
|
||||||
if (!p || !e_iwd || !e_iwd->manager) return;
|
{
|
||||||
Iwd_State s = iwd_manager_state(e_iwd->manager);
|
ERR("e_gadcon_popup_new failed!");
|
||||||
if (p->status_lbl)
|
e_util_dialog_show("IWD Error", "Failed to create gadcon popup");
|
||||||
elm_object_text_set(p->status_lbl, _state_label(s));
|
return;
|
||||||
if (p->btn_toggle)
|
}
|
||||||
elm_object_text_set(p->btn_toggle, s == IWD_STATE_OFF ? "Enable" : "Disable");
|
|
||||||
if (p->btn_scan)
|
|
||||||
elm_object_disabled_set(p->btn_scan, s == IWD_STATE_OFF);
|
|
||||||
if (p->btn_hidden)
|
|
||||||
elm_object_disabled_set(p->btn_hidden, s == IWD_STATE_OFF);
|
|
||||||
if (p->btn_disconnect)
|
|
||||||
{
|
|
||||||
Eina_Bool show = (s == IWD_STATE_CONNECTED);
|
|
||||||
if (show) evas_object_show(p->btn_disconnect);
|
|
||||||
else evas_object_hide(p->btn_disconnect);
|
|
||||||
}
|
|
||||||
_rebuild_list(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
inst->popup = (void *)popup;
|
||||||
_on_manager_change(void *data, Iwd_Manager *m EINA_UNUSED)
|
INF("Popup created: %p", popup);
|
||||||
{
|
|
||||||
_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);
|
|
||||||
|
|
||||||
|
/* 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);
|
evas_object_show(box);
|
||||||
e_gadcon_popup_content_set(p->gp, box);
|
|
||||||
e_gadcon_popup_show(p->gp);
|
|
||||||
|
|
||||||
if (e_iwd->manager)
|
/* Title */
|
||||||
{
|
label = elm_label_add(box);
|
||||||
iwd_manager_listener_add(e_iwd->manager, _on_manager_change, p);
|
elm_object_text_set(label, "<b>IWD Wi-Fi Manager</b>");
|
||||||
p->listening = EINA_TRUE;
|
elm_box_pack_end(box, label);
|
||||||
iwd_manager_set_passphrase_handler(e_iwd->manager, _on_passphrase_request, NULL);
|
evas_object_show(label);
|
||||||
iwd_manager_scan_request(e_iwd->manager);
|
|
||||||
}
|
/* Current connection status */
|
||||||
_refresh(p);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,187 +1,319 @@
|
||||||
#include "iwd_agent.h"
|
#include "iwd_agent.h"
|
||||||
#include "iwd_dbus.h"
|
#include "iwd_dbus.h"
|
||||||
#include <stdlib.h>
|
#include "../e_mod_main.h"
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#define IWD_AGENT_PATH "/net/eiwd/agent"
|
/* Global agent */
|
||||||
|
IWD_Agent *iwd_agent = NULL;
|
||||||
|
|
||||||
struct _Iwd_Agent
|
/* Forward declarations */
|
||||||
{
|
static Eldbus_Message *_agent_request_passphrase(const Eldbus_Service_Interface *iface, const Eldbus_Message *msg);
|
||||||
Eldbus_Connection *conn;
|
static Eldbus_Message *_agent_cancel(const Eldbus_Service_Interface *iface, const Eldbus_Message *msg);
|
||||||
Eldbus_Service_Interface *svc;
|
static Eldbus_Message *_agent_release(const Eldbus_Service_Interface *iface, const Eldbus_Message *msg);
|
||||||
Eldbus_Object *am_obj;
|
static void _agent_register_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending);
|
||||||
Eldbus_Proxy *am_proxy;
|
static void _agent_unregister(void);
|
||||||
Iwd_Agent_Passphrase_Cb cb;
|
|
||||||
void *data;
|
|
||||||
Iwd_Agent_Cancel_Cb cancel_cb;
|
|
||||||
void *cancel_data;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct _Iwd_Agent_Request
|
/* Agent interface methods */
|
||||||
{
|
static const Eldbus_Method agent_methods[] = {
|
||||||
Iwd_Agent *agent;
|
{
|
||||||
Eldbus_Message *msg; /* original RequestPassphrase message */
|
"RequestPassphrase", ELDBUS_ARGS({"o", "network"}),
|
||||||
};
|
ELDBUS_ARGS({"s", "passphrase"}),
|
||||||
|
_agent_request_passphrase, 0
|
||||||
static Iwd_Agent *_self = NULL; /* one agent per process is plenty */
|
},
|
||||||
|
{
|
||||||
/* ----- Method handlers ------------------------------------------------- */
|
"Cancel", ELDBUS_ARGS({"s", "reason"}),
|
||||||
|
NULL,
|
||||||
static Eldbus_Message *
|
_agent_cancel, 0
|
||||||
_release_cb(const Eldbus_Service_Interface *iface EINA_UNUSED,
|
},
|
||||||
const Eldbus_Message *msg)
|
{
|
||||||
{
|
"Release", NULL, NULL,
|
||||||
return eldbus_message_method_return_new(msg);
|
_agent_release, 0
|
||||||
}
|
},
|
||||||
|
|
||||||
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 },
|
|
||||||
{ NULL, NULL, NULL, NULL, 0 }
|
{ NULL, NULL, NULL, NULL, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
static const Eldbus_Service_Interface_Desc _iface_desc = {
|
/* Agent interface description */
|
||||||
IWD_IFACE_AGENT, _methods, NULL, NULL, NULL, NULL
|
static const Eldbus_Service_Interface_Desc agent_desc = {
|
||||||
|
IWD_AGENT_MANAGER_INTERFACE, agent_methods, NULL, NULL, NULL, NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ----- Reply / cancel from the UI ------------------------------------- */
|
/* Initialize agent */
|
||||||
|
Eina_Bool
|
||||||
void
|
iwd_agent_init(void)
|
||||||
iwd_agent_reply(Iwd_Agent_Request *req, const char *passphrase)
|
|
||||||
{
|
{
|
||||||
if (!req) return;
|
Eldbus_Connection *conn;
|
||||||
Eldbus_Message *r = eldbus_message_method_return_new(req->msg);
|
Eldbus_Object *obj;
|
||||||
eldbus_message_arguments_append(r, "s", passphrase ? passphrase : "");
|
Eldbus_Proxy *proxy;
|
||||||
eldbus_connection_send(req->agent->conn, r, NULL, NULL, -1);
|
|
||||||
eldbus_message_unref(req->msg);
|
DBG("Initializing iwd agent");
|
||||||
free(req);
|
|
||||||
|
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
|
void
|
||||||
iwd_agent_cancel(Iwd_Agent_Request *req)
|
iwd_agent_shutdown(void)
|
||||||
{
|
{
|
||||||
if (!req) return;
|
DBG("Shutting down iwd agent");
|
||||||
Eldbus_Message *e = eldbus_message_error_new(req->msg,
|
|
||||||
"net.connman.iwd.Agent.Error.Canceled",
|
if (!iwd_agent) return;
|
||||||
"User canceled");
|
|
||||||
eldbus_connection_send(req->agent->conn, e, NULL, NULL, -1);
|
_agent_unregister();
|
||||||
eldbus_message_unref(req->msg);
|
|
||||||
free(req);
|
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
|
static void
|
||||||
_on_register(void *data EINA_UNUSED, const Eldbus_Message *msg,
|
_agent_register_cb(void *data EINA_UNUSED,
|
||||||
Eldbus_Pending *p EINA_UNUSED)
|
const Eldbus_Message *msg,
|
||||||
|
Eldbus_Pending *pending EINA_UNUSED)
|
||||||
{
|
{
|
||||||
const char *en, *em;
|
const char *err_name, *err_msg;
|
||||||
if (eldbus_message_error_get(msg, &en, &em))
|
|
||||||
fprintf(stderr, "e_iwd: agent register failed: %s: %s\n", en, em);
|
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 *
|
/* Unregister agent */
|
||||||
iwd_agent_new(Eldbus_Connection *conn, Iwd_Agent_Passphrase_Cb cb, void *data)
|
static void
|
||||||
|
_agent_unregister(void)
|
||||||
{
|
{
|
||||||
Iwd_Agent *a = calloc(1, sizeof(*a));
|
Eldbus_Connection *conn;
|
||||||
if (!a) return NULL;
|
Eldbus_Object *obj;
|
||||||
a->conn = conn;
|
Eldbus_Proxy *proxy;
|
||||||
a->cb = cb;
|
|
||||||
a->data = data;
|
|
||||||
_self = a;
|
|
||||||
|
|
||||||
a->svc = eldbus_service_interface_register(conn, IWD_AGENT_PATH, &_iface_desc);
|
conn = iwd_dbus_conn_get();
|
||||||
if (!a->svc) { free(a); _self = NULL; return NULL; }
|
if (!conn) return;
|
||||||
|
|
||||||
a->am_obj = eldbus_object_get(conn, IWD_BUS_NAME, "/net/connman/iwd");
|
obj = eldbus_object_get(conn, IWD_SERVICE, IWD_DAEMON_PATH);
|
||||||
if (a->am_obj)
|
if (!obj) return;
|
||||||
{
|
|
||||||
a->am_proxy = eldbus_proxy_get(a->am_obj, IWD_IFACE_AGENT_MANAGER);
|
proxy = eldbus_proxy_get(obj, IWD_AGENT_MANAGER_INTERFACE);
|
||||||
if (a->am_proxy)
|
if (proxy)
|
||||||
eldbus_proxy_call(a->am_proxy, "RegisterAgent", _on_register, NULL, -1,
|
{
|
||||||
"o", IWD_AGENT_PATH);
|
eldbus_proxy_call(proxy, "UnregisterAgent", NULL, NULL, -1, "o", IWD_AGENT_PATH);
|
||||||
}
|
DBG("Agent unregistered from iwd");
|
||||||
return a;
|
}
|
||||||
|
|
||||||
|
eldbus_object_unref(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
/* Request passphrase method */
|
||||||
iwd_agent_set_cancel_cb(Iwd_Agent *a, Iwd_Agent_Cancel_Cb cb, void *data)
|
static Eldbus_Message *
|
||||||
|
_agent_request_passphrase(const Eldbus_Service_Interface *iface EINA_UNUSED,
|
||||||
|
const Eldbus_Message *msg)
|
||||||
{
|
{
|
||||||
if (!a) return;
|
const char *network_path;
|
||||||
a->cancel_cb = cb;
|
IWD_Network *net;
|
||||||
a->cancel_data = data;
|
|
||||||
|
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
|
/* Cancel method */
|
||||||
iwd_agent_free(Iwd_Agent *a)
|
static Eldbus_Message *
|
||||||
|
_agent_cancel(const Eldbus_Service_Interface *iface EINA_UNUSED,
|
||||||
|
const Eldbus_Message *msg)
|
||||||
{
|
{
|
||||||
if (!a) return;
|
const char *reason;
|
||||||
if (a->svc) eldbus_service_interface_unregister(a->svc);
|
|
||||||
if (a->am_proxy) eldbus_proxy_unref(a->am_proxy);
|
if (!eldbus_message_arguments_get(msg, "s", &reason))
|
||||||
if (a->am_obj) eldbus_object_unref(a->am_obj);
|
{
|
||||||
if (_self == a) _self = NULL;
|
WRN("Cancel called with no reason");
|
||||||
free(a);
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,32 @@
|
||||||
#ifndef IWD_AGENT_H
|
#ifndef IWD_AGENT_H
|
||||||
#define IWD_AGENT_H
|
#define IWD_AGENT_H
|
||||||
|
|
||||||
|
#include <Eina.h>
|
||||||
#include <Eldbus.h>
|
#include <Eldbus.h>
|
||||||
|
|
||||||
typedef struct _Iwd_Agent Iwd_Agent;
|
#define IWD_AGENT_PATH "/org/enlightenment/eiwd/agent"
|
||||||
typedef struct _Iwd_Agent_Request Iwd_Agent_Request;
|
|
||||||
|
|
||||||
/* The UI registers a single handler that is called whenever iwd asks for
|
/* Agent structure */
|
||||||
* a passphrase. The handler must eventually call iwd_agent_reply() or
|
typedef struct _IWD_Agent
|
||||||
* iwd_agent_cancel() with the request token. */
|
{
|
||||||
typedef void (*Iwd_Agent_Passphrase_Cb)(void *data,
|
Eldbus_Service_Interface *iface;
|
||||||
Iwd_Agent_Request *req,
|
Eldbus_Object *manager_obj; /* Keep reference to prevent call cancellation */
|
||||||
const char *network_path);
|
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
|
/* Global agent */
|
||||||
* request — the UI should tear down any open auth dialog. */
|
extern IWD_Agent *iwd_agent;
|
||||||
typedef void (*Iwd_Agent_Cancel_Cb)(void *data, const char *reason);
|
|
||||||
|
|
||||||
Iwd_Agent *iwd_agent_new (Eldbus_Connection *conn,
|
/* Agent management */
|
||||||
Iwd_Agent_Passphrase_Cb cb, void *data);
|
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);
|
/* Set passphrase for pending request */
|
||||||
void iwd_agent_free(Iwd_Agent *a);
|
void iwd_agent_set_passphrase(const char *passphrase);
|
||||||
|
|
||||||
void iwd_agent_reply (Iwd_Agent_Request *req, const char *passphrase);
|
/* Cancel pending request */
|
||||||
void iwd_agent_cancel(Iwd_Agent_Request *req);
|
void iwd_agent_cancel(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,161 +1,398 @@
|
||||||
#include "iwd_dbus.h"
|
#include "iwd_dbus.h"
|
||||||
#include <stdlib.h>
|
#include "../e_mod_main.h"
|
||||||
#include <string.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;
|
DBG("Initializing iwd D-Bus connection");
|
||||||
Iwd_Dbus_Callbacks cbs;
|
|
||||||
void *data;
|
|
||||||
|
|
||||||
Eldbus_Object *root_obj;
|
if (iwd_dbus)
|
||||||
Eldbus_Proxy *root_om;
|
{
|
||||||
Eldbus_Signal_Handler *sh_added;
|
WRN("D-Bus already initialized");
|
||||||
Eldbus_Signal_Handler *sh_removed;
|
return EINA_TRUE;
|
||||||
Eina_Bool present;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Eldbus_Connection *
|
iwd_dbus = E_NEW(IWD_DBus, 1);
|
||||||
iwd_dbus_conn(const Iwd_Dbus *d) { return d ? d->conn : NULL; }
|
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
|
/* Connect to system bus */
|
||||||
* for every (path, interface) pair. */
|
iwd_dbus->conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM);
|
||||||
static void
|
if (!iwd_dbus->conn)
|
||||||
_emit_managed(Iwd_Dbus *d, Eldbus_Message_Iter *objects)
|
{
|
||||||
{
|
ERR("Failed to connect to system bus");
|
||||||
Eldbus_Message_Iter *entry;
|
E_FREE(iwd_dbus);
|
||||||
while (eldbus_message_iter_get_and_next(objects, 'e', &entry))
|
iwd_dbus = NULL;
|
||||||
{
|
return EINA_FALSE;
|
||||||
const char *path;
|
}
|
||||||
Eldbus_Message_Iter *ifaces;
|
|
||||||
if (!eldbus_message_iter_arguments_get(entry, "oa{sa{sv}}", &path, &ifaces))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Eldbus_Message_Iter *iface_entry;
|
/* Monitor iwd daemon availability */
|
||||||
while (eldbus_message_iter_get_and_next(ifaces, 'e', &iface_entry))
|
eldbus_name_owner_changed_callback_add(iwd_dbus->conn,
|
||||||
{
|
IWD_SERVICE,
|
||||||
const char *iface;
|
_iwd_dbus_name_owner_changed_cb,
|
||||||
Eldbus_Message_Iter *props;
|
NULL,
|
||||||
if (!eldbus_message_iter_arguments_get(iface_entry, "sa{sv}", &iface, &props))
|
EINA_TRUE);
|
||||||
continue;
|
|
||||||
if (d->cbs.iface_added)
|
/* Try to connect to iwd */
|
||||||
d->cbs.iface_added(d->data, path, iface, props);
|
_iwd_dbus_connect();
|
||||||
}
|
|
||||||
}
|
return EINA_TRUE;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Shutdown D-Bus connection */
|
||||||
void
|
void
|
||||||
iwd_dbus_free(Iwd_Dbus *d)
|
iwd_dbus_shutdown(void)
|
||||||
{
|
{
|
||||||
if (!d) return;
|
DBG("Shutting down iwd D-Bus connection");
|
||||||
eldbus_name_owner_changed_callback_del(d->conn, IWD_BUS_NAME, _on_name_owner, d);
|
|
||||||
_unbind_root(d);
|
if (!iwd_dbus) return;
|
||||||
free(d);
|
|
||||||
|
_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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,50 @@
|
||||||
#ifndef IWD_DBUS_H
|
#ifndef IWD_DBUS_H
|
||||||
#define IWD_DBUS_H
|
#define IWD_DBUS_H
|
||||||
|
|
||||||
#include <Eldbus.h>
|
|
||||||
#include <Eina.h>
|
#include <Eina.h>
|
||||||
|
#include <Eldbus.h>
|
||||||
|
|
||||||
#define IWD_BUS_NAME "net.connman.iwd"
|
/* iwd D-Bus service and interfaces */
|
||||||
#define IWD_IFACE_ADAPTER "net.connman.iwd.Adapter"
|
#define IWD_SERVICE "net.connman.iwd"
|
||||||
#define IWD_IFACE_DEVICE "net.connman.iwd.Device"
|
#define IWD_MANAGER_PATH "/"
|
||||||
#define IWD_IFACE_STATION "net.connman.iwd.Station"
|
#define IWD_DAEMON_PATH "/net/connman/iwd"
|
||||||
#define IWD_IFACE_NETWORK "net.connman.iwd.Network"
|
#define IWD_MANAGER_INTERFACE "net.connman.iwd.Manager"
|
||||||
#define IWD_IFACE_KNOWN_NETWORK "net.connman.iwd.KnownNetwork"
|
#define IWD_ADAPTER_INTERFACE "net.connman.iwd.Adapter"
|
||||||
#define IWD_IFACE_AGENT_MANAGER "net.connman.iwd.AgentManager"
|
#define IWD_DEVICE_INTERFACE "net.connman.iwd.Device"
|
||||||
#define IWD_IFACE_AGENT "net.connman.iwd.Agent"
|
#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. */
|
/* D-Bus context */
|
||||||
typedef struct _Iwd_Dbus_Callbacks
|
typedef struct _IWD_DBus
|
||||||
{
|
{
|
||||||
void (*name_appeared) (void *data);
|
Eldbus_Connection *conn;
|
||||||
void (*name_vanished) (void *data);
|
Eldbus_Object *manager_obj;
|
||||||
/* iface_props is an iter positioned on a{sv} for the given interface. */
|
Eldbus_Proxy *manager_proxy;
|
||||||
void (*iface_added) (void *data, const char *path, const char *iface,
|
Eldbus_Signal_Handler *interfaces_added;
|
||||||
Eldbus_Message_Iter *iface_props);
|
Eldbus_Signal_Handler *interfaces_removed;
|
||||||
void (*iface_removed) (void *data, const char *path, const char *iface);
|
|
||||||
} Iwd_Dbus_Callbacks;
|
|
||||||
|
|
||||||
Iwd_Dbus *iwd_dbus_new (Eldbus_Connection *conn,
|
Eina_Bool connected;
|
||||||
const Iwd_Dbus_Callbacks *cbs, void *data);
|
} IWD_DBus;
|
||||||
void iwd_dbus_free(Iwd_Dbus *d);
|
|
||||||
|
|
||||||
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
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,220 +1,290 @@
|
||||||
#include "iwd_device.h"
|
#include "iwd_device.h"
|
||||||
#include "iwd_dbus.h"
|
#include "iwd_dbus.h"
|
||||||
#include "iwd_props.h"
|
#include "../e_mod_main.h"
|
||||||
#include "iwd_manager.h"
|
|
||||||
#include "iwd_network.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
static void _refresh_signals(Iwd_Device *d);
|
/* Global device list */
|
||||||
|
Eina_List *iwd_devices = NULL;
|
||||||
|
|
||||||
static Iwd_Station_State
|
/* Forward declarations */
|
||||||
_state_from_str(const char *s)
|
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;
|
IWD_Device *dev;
|
||||||
if (!strcmp(s, "connected")) return IWD_STATION_CONNECTED;
|
Eldbus_Connection *conn;
|
||||||
if (!strcmp(s, "connecting")) return IWD_STATION_CONNECTING;
|
Eldbus_Object *obj;
|
||||||
if (!strcmp(s, "disconnecting")) return IWD_STATION_DISCONNECTING;
|
|
||||||
if (!strcmp(s, "roaming")) return IWD_STATION_ROAMING;
|
if (!path) return NULL;
|
||||||
return IWD_STATION_DISCONNECTED;
|
|
||||||
}
|
conn = iwd_dbus_conn_get();
|
||||||
|
if (!conn) return NULL;
|
||||||
static void
|
|
||||||
_dev_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v)
|
/* Check if device already exists */
|
||||||
{
|
dev = iwd_device_find(path);
|
||||||
Iwd_Device *d = data;
|
if (dev)
|
||||||
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); }
|
DBG("Device already exists: %s", path);
|
||||||
else if (!strcmp(key, "Adapter")) { free(d->adapter_path); d->adapter_path = iwd_props_str_dup(v); }
|
return dev;
|
||||||
else if (!strcmp(key, "Powered")) { d->powered = iwd_props_bool(v); }
|
}
|
||||||
}
|
|
||||||
|
DBG("Creating new device: %s", path);
|
||||||
static void
|
|
||||||
_sta_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v)
|
dev = E_NEW(IWD_Device, 1);
|
||||||
{
|
if (!dev) return NULL;
|
||||||
Iwd_Device *d = data;
|
|
||||||
if (!strcmp(key, "State"))
|
dev->path = eina_stringshare_add(path);
|
||||||
{
|
|
||||||
char *s = iwd_props_str_dup(v);
|
/* Create D-Bus object */
|
||||||
d->station_state = _state_from_str(s);
|
obj = eldbus_object_get(conn, IWD_SERVICE, path);
|
||||||
free(s);
|
if (!obj)
|
||||||
}
|
{
|
||||||
else if (!strcmp(key, "Scanning"))
|
ERR("Failed to get D-Bus object for device");
|
||||||
{
|
eina_stringshare_del(dev->path);
|
||||||
Eina_Bool was = d->scanning;
|
E_FREE(dev);
|
||||||
d->scanning = iwd_props_bool(v);
|
return NULL;
|
||||||
/* When a scan finishes, ask iwd for the ranked list with RSSI. */
|
}
|
||||||
if (was && !d->scanning) _refresh_signals(d);
|
|
||||||
}
|
/* Get proxies */
|
||||||
else if (!strcmp(key, "ConnectedNetwork")) { free(d->connected_network); d->connected_network = iwd_props_str_dup(v); }
|
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
|
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
|
void
|
||||||
iwd_device_apply_station_props(Iwd_Device *d, Eldbus_Message_Iter *props)
|
iwd_device_scan(IWD_Device *dev)
|
||||||
{
|
{
|
||||||
d->has_station = EINA_TRUE;
|
if (!dev || !dev->station_proxy)
|
||||||
iwd_props_for_each(props, _sta_prop_cb, d);
|
{
|
||||||
|
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) */
|
/* Disconnect from network */
|
||||||
static void
|
void
|
||||||
_on_dev_props_changed(void *data, const Eldbus_Message *msg)
|
iwd_device_disconnect(IWD_Device *dev)
|
||||||
{
|
{
|
||||||
Iwd_Device *d = data;
|
if (!dev || !dev->station_proxy)
|
||||||
const char *iface;
|
{
|
||||||
|
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;
|
Eldbus_Message_Iter *changed, *invalidated;
|
||||||
if (!eldbus_message_arguments_get(msg, "sa{sv}as", &iface, &changed, &invalidated))
|
|
||||||
return;
|
if (!eldbus_message_arguments_get(msg, "sa{sv}as", &interface, &changed, &invalidated))
|
||||||
if (strcmp(iface, IWD_IFACE_DEVICE) != 0) return;
|
{
|
||||||
iwd_props_for_each(changed, _dev_prop_cb, d);
|
ERR("Failed to parse PropertiesChanged signal");
|
||||||
if (d->manager) iwd_manager_notify(d->manager);
|
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
|
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;
|
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
|
if (!properties) return;
|
||||||
_refresh_signals(Iwd_Device *d)
|
|
||||||
{
|
|
||||||
if (!d || !d->station_proxy) return;
|
|
||||||
eldbus_proxy_call(d->station_proxy, "GetOrderedNetworks",
|
|
||||||
_on_ordered_networks, d, -1, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
while (eldbus_message_iter_get_and_next(properties, 'e', &entry))
|
||||||
iwd_device_scan(Iwd_Device *d)
|
{
|
||||||
{
|
const char *key;
|
||||||
if (!d || !d->station_proxy) return;
|
Eldbus_Message_Iter *var;
|
||||||
eldbus_proxy_call(d->station_proxy, "Scan", NULL, NULL, -1, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
if (!eldbus_message_iter_arguments_get(entry, "sv", &key, &var))
|
||||||
iwd_device_disconnect(Iwd_Device *d)
|
continue;
|
||||||
{
|
|
||||||
if (!d || !d->station_proxy) return;
|
|
||||||
eldbus_proxy_call(d->station_proxy, "Disconnect", NULL, NULL, -1, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
if (strcmp(key, "Name") == 0)
|
||||||
_on_connect_hidden_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
|
{
|
||||||
{
|
const char *name;
|
||||||
const char *en, *em;
|
if (eldbus_message_iter_arguments_get(var, "s", &name))
|
||||||
char *ssid = data;
|
{
|
||||||
if (eldbus_message_error_get(msg, &en, &em))
|
eina_stringshare_replace(&dev->name, name);
|
||||||
fprintf(stderr, "e_iwd: ConnectHiddenNetwork('%s') failed: %s: %s\n",
|
DBG(" Name: %s", dev->name);
|
||||||
ssid ? ssid : "?", en, em);
|
}
|
||||||
free(ssid);
|
}
|
||||||
}
|
else if (strcmp(key, "Address") == 0)
|
||||||
|
{
|
||||||
void
|
const char *address;
|
||||||
iwd_device_connect_hidden(Iwd_Device *d, const char *ssid)
|
if (eldbus_message_iter_arguments_get(var, "s", &address))
|
||||||
{
|
{
|
||||||
if (!d || !d->station_proxy || !ssid || !*ssid) return;
|
eina_stringshare_replace(&dev->address, address);
|
||||||
eldbus_proxy_call(d->station_proxy, "ConnectHiddenNetwork",
|
DBG(" Address: %s", dev->address);
|
||||||
_on_connect_hidden_reply, strdup(ssid), -1, "s", ssid);
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,49 +4,45 @@
|
||||||
#include <Eina.h>
|
#include <Eina.h>
|
||||||
#include <Eldbus.h>
|
#include <Eldbus.h>
|
||||||
|
|
||||||
typedef struct _Iwd_Device Iwd_Device;
|
/* Device structure */
|
||||||
|
typedef struct _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
|
|
||||||
{
|
{
|
||||||
char *path;
|
const char *path; /* D-Bus object path */
|
||||||
char *name;
|
const char *name; /* Interface name (e.g., "wlan0") */
|
||||||
char *address;
|
const char *address; /* MAC address */
|
||||||
char *adapter_path;
|
const char *adapter_path; /* Adapter object path */
|
||||||
Eina_Bool powered;
|
const char *mode; /* "station", "ap", "ad-hoc" */
|
||||||
|
Eina_Bool powered; /* Device powered state */
|
||||||
|
|
||||||
/* Station-side state, valid when station_proxy != NULL */
|
/* Station interface properties */
|
||||||
Eina_Bool has_station;
|
Eina_Bool scanning;
|
||||||
Iwd_Station_State station_state;
|
const char *state; /* "disconnected", "connecting", "connected", "disconnecting" */
|
||||||
Eina_Bool scanning;
|
const char *connected_network; /* Connected network object path */
|
||||||
char *connected_network;
|
|
||||||
|
|
||||||
Eldbus_Object *obj;
|
/* D-Bus objects */
|
||||||
Eldbus_Proxy *device_proxy;
|
Eldbus_Proxy *device_proxy;
|
||||||
Eldbus_Proxy *station_proxy;
|
Eldbus_Proxy *station_proxy;
|
||||||
Eldbus_Signal_Handler *sh_dev_props;
|
Eldbus_Signal_Handler *properties_changed;
|
||||||
Eldbus_Signal_Handler *sh_sta_props;
|
} 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);
|
/* Device management */
|
||||||
void iwd_device_free(Iwd_Device *d);
|
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);
|
/* Device operations */
|
||||||
void iwd_device_apply_station_props(Iwd_Device *d, Eldbus_Message_Iter *props);
|
void iwd_device_scan(IWD_Device *dev);
|
||||||
void iwd_device_attach_station (Iwd_Device *d);
|
void iwd_device_disconnect(IWD_Device *dev);
|
||||||
void iwd_device_detach_station (Iwd_Device *d);
|
void iwd_device_connect_hidden(IWD_Device *dev, const char *ssid);
|
||||||
|
|
||||||
void iwd_device_scan (Iwd_Device *d);
|
/* Get devices list */
|
||||||
void iwd_device_disconnect (Iwd_Device *d);
|
Eina_List *iwd_devices_get(void);
|
||||||
void iwd_device_connect_hidden (Iwd_Device *d, const char *ssid);
|
|
||||||
|
/* Initialize device subsystem */
|
||||||
|
void iwd_device_init(void);
|
||||||
|
void iwd_device_shutdown(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,131 +1,288 @@
|
||||||
#include "iwd_network.h"
|
#include "iwd_network.h"
|
||||||
#include "iwd_dbus.h"
|
#include "iwd_dbus.h"
|
||||||
#include "iwd_props.h"
|
#include "../e_mod_main.h"
|
||||||
#include "iwd_manager.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
static Iwd_Security
|
/* Global network list */
|
||||||
_sec_from_str(const char *s)
|
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;
|
IWD_Network *net;
|
||||||
if (!strcmp(s, "open")) return IWD_SEC_OPEN;
|
Eldbus_Connection *conn;
|
||||||
if (!strcmp(s, "psk")) return IWD_SEC_PSK;
|
Eldbus_Object *obj;
|
||||||
if (!strcmp(s, "8021x")) return IWD_SEC_8021X;
|
|
||||||
if (!strcmp(s, "wep")) return IWD_SEC_WEP;
|
if (!path) return NULL;
|
||||||
return IWD_SEC_UNKNOWN;
|
|
||||||
}
|
conn = iwd_dbus_conn_get();
|
||||||
|
if (!conn) return NULL;
|
||||||
static void
|
|
||||||
_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v)
|
/* Check if network already exists */
|
||||||
{
|
net = iwd_network_find(path);
|
||||||
Iwd_Network *n = data;
|
if (net)
|
||||||
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); }
|
DBG("Network already exists: %s", path);
|
||||||
else if (!strcmp(key, "Connected")) { n->connected = iwd_props_bool(v); }
|
return net;
|
||||||
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); }
|
|
||||||
|
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
|
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
|
/* Find network by path */
|
||||||
_on_props_changed(void *data, const Eldbus_Message *msg)
|
IWD_Network *
|
||||||
|
iwd_network_find(const char *path)
|
||||||
{
|
{
|
||||||
Iwd_Network *n = data;
|
Eina_List *l;
|
||||||
const char *iface;
|
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;
|
Eldbus_Message_Iter *changed, *invalidated;
|
||||||
if (!eldbus_message_arguments_get(msg, "sa{sv}as", &iface, &changed, &invalidated))
|
|
||||||
return;
|
if (!eldbus_message_arguments_get(msg, "sa{sv}as", &interface, &changed, &invalidated))
|
||||||
if (strcmp(iface, IWD_IFACE_NETWORK) != 0) return;
|
{
|
||||||
iwd_props_for_each(changed, _prop_cb, n);
|
ERR("Failed to parse PropertiesChanged signal");
|
||||||
if (n->manager) iwd_manager_notify(n->manager);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Iwd_Network *
|
DBG("Properties changed for network %s on interface %s", net->path, interface);
|
||||||
iwd_network_new(Eldbus_Connection *conn, const char *path, void *manager)
|
|
||||||
{
|
_network_parse_properties(net, changed);
|
||||||
Iwd_Network *n = calloc(1, sizeof(*n));
|
|
||||||
if (!n) return NULL;
|
/* TODO: Notify UI of changes */
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Parse network properties */
|
||||||
static void
|
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;
|
Eldbus_Message_Iter *entry;
|
||||||
const char *ssid = data;
|
|
||||||
if (eldbus_message_error_get(msg, &en, &em))
|
|
||||||
fprintf(stderr, "e_iwd: connect to '%s' failed: %s: %s\n",
|
|
||||||
ssid ? ssid : "?", en, em);
|
|
||||||
free(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
if (!properties) return;
|
||||||
iwd_network_signal_tier(const Iwd_Network *n)
|
|
||||||
{
|
|
||||||
if (!n || !n->have_signal) return 0;
|
|
||||||
/* iwd reports signal in 100*dBm. Cutoffs in dBm: -60/-67/-74/-80. */
|
|
||||||
int dbm = n->signal_dbm / 100;
|
|
||||||
if (dbm >= -60) return 4;
|
|
||||||
if (dbm >= -67) return 3;
|
|
||||||
if (dbm >= -74) return 2;
|
|
||||||
if (dbm >= -80) return 1;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
while (eldbus_message_iter_get_and_next(properties, 'e', &entry))
|
||||||
iwd_network_connect(Iwd_Network *n)
|
{
|
||||||
{
|
const char *key;
|
||||||
if (!n || !n->proxy) return;
|
Eldbus_Message_Iter *var;
|
||||||
/* Network.Connect() takes no args; iwd will dial the registered Agent
|
|
||||||
* for a passphrase if needed. */
|
|
||||||
eldbus_proxy_call(n->proxy, "Connect", _on_connect_reply,
|
|
||||||
n->ssid ? strdup(n->ssid) : NULL, -1, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
if (!eldbus_message_iter_arguments_get(entry, "sv", &key, &var))
|
||||||
iwd_network_forget(Iwd_Network *n)
|
continue;
|
||||||
{
|
|
||||||
if (!n || !n->known_path || !n->obj) return;
|
if (strcmp(key, "Name") == 0)
|
||||||
Eldbus_Connection *conn = eldbus_object_connection_get(n->obj);
|
{
|
||||||
Eldbus_Object *kobj = eldbus_object_get(conn, IWD_BUS_NAME, n->known_path);
|
const char *name;
|
||||||
if (!kobj) return;
|
if (eldbus_message_iter_arguments_get(var, "s", &name))
|
||||||
Eldbus_Proxy *kp = eldbus_proxy_get(kobj, IWD_IFACE_KNOWN_NETWORK);
|
{
|
||||||
if (kp)
|
eina_stringshare_replace(&net->name, name);
|
||||||
{
|
DBG(" Name: %s", net->name);
|
||||||
eldbus_proxy_call(kp, "Forget", NULL, NULL, -1, "");
|
}
|
||||||
eldbus_proxy_unref(kp);
|
}
|
||||||
}
|
else if (strcmp(key, "Type") == 0)
|
||||||
eldbus_object_unref(kobj);
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,44 @@
|
||||||
#ifndef IWD_NETWORK_H
|
#ifndef IWD_NETWORK_H
|
||||||
#define IWD_NETWORK_H
|
#define IWD_NETWORK_H
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <Eina.h>
|
#include <Eina.h>
|
||||||
#include <Eldbus.h>
|
#include <Eldbus.h>
|
||||||
|
|
||||||
typedef enum {
|
/* Network structure */
|
||||||
IWD_SEC_OPEN,
|
typedef struct _IWD_Network
|
||||||
IWD_SEC_PSK,
|
|
||||||
IWD_SEC_8021X,
|
|
||||||
IWD_SEC_WEP,
|
|
||||||
IWD_SEC_UNKNOWN,
|
|
||||||
} Iwd_Security;
|
|
||||||
|
|
||||||
typedef struct _Iwd_Network Iwd_Network;
|
|
||||||
|
|
||||||
struct _Iwd_Network
|
|
||||||
{
|
{
|
||||||
char *path;
|
const char *path; /* D-Bus object path */
|
||||||
char *ssid;
|
const char *name; /* SSID (decoded) */
|
||||||
char *device_path;
|
const char *type; /* "open", "psk", "8021x" */
|
||||||
char *known_path;
|
Eina_Bool known; /* Is this a known network? */
|
||||||
Iwd_Security security;
|
int16_t signal_strength; /* Signal strength in dBm */
|
||||||
Eina_Bool connected;
|
|
||||||
|
|
||||||
/* Signal strength in 100*dBm units (iwd convention). Valid iff have_signal. */
|
/* Known network properties */
|
||||||
int16_t signal_dbm;
|
Eina_Bool auto_connect;
|
||||||
Eina_Bool have_signal;
|
const char *last_connected_time;
|
||||||
|
|
||||||
Eldbus_Object *obj;
|
/* D-Bus objects */
|
||||||
Eldbus_Proxy *proxy;
|
Eldbus_Proxy *network_proxy;
|
||||||
Eldbus_Signal_Handler *sh_props;
|
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);
|
/* Network management */
|
||||||
void iwd_network_free(Iwd_Network *n);
|
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. */
|
/* Get networks list */
|
||||||
int iwd_network_signal_tier(const Iwd_Network *n);
|
Eina_List *iwd_networks_get(void);
|
||||||
|
|
||||||
void iwd_network_connect (Iwd_Network *n);
|
/* Initialize network subsystem */
|
||||||
/* Forget acts on the KnownNetwork object referenced by this network. */
|
void iwd_network_init(void);
|
||||||
void iwd_network_forget (Iwd_Network *n);
|
void iwd_network_shutdown(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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
153
src/iwd/iwd_state.c
Normal 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
41
src/iwd/iwd_state.h
Normal 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
|
||||||
|
|
@ -1,27 +1,36 @@
|
||||||
e_iwd_sources = [
|
module_sources = files(
|
||||||
'e_mod_main.c',
|
'e_mod_main.c',
|
||||||
'e_mod_config.c',
|
'e_mod_config.c',
|
||||||
'e_mod_gadget.c',
|
'e_mod_gadget.c',
|
||||||
'e_mod_popup.c',
|
'e_mod_popup.c',
|
||||||
'iwd/iwd_dbus.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_device.c',
|
||||||
'iwd/iwd_network.c',
|
'iwd/iwd_network.c',
|
||||||
'ui/wifi_list.c',
|
'iwd/iwd_agent.c',
|
||||||
|
'iwd/iwd_state.c',
|
||||||
'ui/wifi_auth.c',
|
'ui/wifi_auth.c',
|
||||||
'ui/wifi_hidden.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',
|
shared_module('module',
|
||||||
e_iwd_sources,
|
module_sources,
|
||||||
name_prefix : '',
|
dependencies: module_deps,
|
||||||
name_suffix : 'so',
|
include_directories: module_includes,
|
||||||
dependencies : [eldbus, elementary, enlightenment],
|
name_prefix: '',
|
||||||
include_directories : include_directories('.', 'iwd', 'ui'),
|
install: true,
|
||||||
install : true,
|
install_dir: dir_module_arch
|
||||||
install_dir : join_paths(module_dir, module_arch),
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,115 +1,171 @@
|
||||||
#include "wifi_auth.h"
|
#include "../e_mod_main.h"
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
typedef struct _Auth_Ctx
|
/* Auth dialog structure */
|
||||||
|
typedef struct _Auth_Dialog
|
||||||
{
|
{
|
||||||
Evas_Object *win; /* top-level window hosting the popup */
|
Instance *inst;
|
||||||
Evas_Object *popup;
|
IWD_Network *network;
|
||||||
|
E_Dialog *dialog;
|
||||||
Evas_Object *entry;
|
Evas_Object *entry;
|
||||||
Wifi_Auth_Cb cb;
|
char *passphrase;
|
||||||
void *data;
|
} Auth_Dialog;
|
||||||
Eina_Bool fired;
|
|
||||||
} Auth_Ctx;
|
|
||||||
|
|
||||||
|
/* Global auth dialog (only one at a time) */
|
||||||
|
static Auth_Dialog *auth_dialog = NULL;
|
||||||
|
|
||||||
|
/* Forward declarations */
|
||||||
|
static void _auth_dialog_ok_cb(void *data, E_Dialog *dialog);
|
||||||
|
static void _auth_dialog_cancel_cb(void *data, E_Dialog *dialog);
|
||||||
|
static void _auth_dialog_free(Auth_Dialog *ad);
|
||||||
|
|
||||||
|
/* Show authentication dialog */
|
||||||
|
void
|
||||||
|
wifi_auth_dialog_show(Instance *inst, IWD_Network *net)
|
||||||
|
{
|
||||||
|
Auth_Dialog *ad;
|
||||||
|
E_Dialog *dia;
|
||||||
|
Evas_Object *o, *entry;
|
||||||
|
char buf[512];
|
||||||
|
|
||||||
|
if (!inst || !net) return;
|
||||||
|
|
||||||
|
/* Only one auth dialog at a time */
|
||||||
|
if (auth_dialog)
|
||||||
|
{
|
||||||
|
WRN("Auth dialog already open");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBG("Showing auth dialog for network: %s", net->name ? net->name : net->path);
|
||||||
|
|
||||||
|
ad = E_NEW(Auth_Dialog, 1);
|
||||||
|
if (!ad) return;
|
||||||
|
|
||||||
|
ad->inst = inst;
|
||||||
|
ad->network = net;
|
||||||
|
auth_dialog = ad;
|
||||||
|
|
||||||
|
/* Create dialog */
|
||||||
|
dia = e_dialog_new(NULL, "E", "iwd_passphrase");
|
||||||
|
if (!dia)
|
||||||
|
{
|
||||||
|
_auth_dialog_free(ad);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ad->dialog = dia;
|
||||||
|
|
||||||
|
e_dialog_title_set(dia, "Wi-Fi Authentication");
|
||||||
|
e_dialog_icon_set(dia, "network-wireless", 48);
|
||||||
|
|
||||||
|
/* Message */
|
||||||
|
snprintf(buf, sizeof(buf),
|
||||||
|
"Enter passphrase for network:<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
|
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;
|
Auth_Dialog *ad = data;
|
||||||
c->fired = EINA_TRUE;
|
|
||||||
if (c->cb) c->cb(c->data, pass, ok);
|
if (!ad) return;
|
||||||
if (c->win) evas_object_del(c->win);
|
|
||||||
free(c);
|
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
|
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;
|
Auth_Dialog *ad = data;
|
||||||
_finish(c, EINA_TRUE, elm_entry_entry_get(c->entry));
|
|
||||||
|
DBG("Auth dialog cancelled");
|
||||||
|
|
||||||
|
/* Cancel agent request */
|
||||||
|
iwd_agent_cancel();
|
||||||
|
|
||||||
|
_auth_dialog_free(ad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Free auth dialog */
|
||||||
static void
|
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
|
/* Cancel any open auth dialog */
|
||||||
_on_del(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
void
|
||||||
|
wifi_auth_dialog_cancel(void)
|
||||||
{
|
{
|
||||||
/* Window closed without cancel/ok — treat as cancel. */
|
if (auth_dialog)
|
||||||
_finish(data, EINA_FALSE, NULL);
|
{
|
||||||
}
|
DBG("Cancelling auth dialog from external request");
|
||||||
|
_auth_dialog_free(auth_dialog);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,16 @@
|
||||||
#ifndef WIFI_AUTH_H
|
#ifndef WIFI_AUTH_H
|
||||||
#define 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
|
/* Show authentication dialog for a network */
|
||||||
* (e.g. "WPA", "WEP") shown alongside the SSID; pass NULL to omit it.
|
void wifi_auth_dialog_show(Instance *inst, IWD_Network *net);
|
||||||
* cb is called exactly once with ok=EINA_TRUE + passphrase, or
|
|
||||||
* ok=EINA_FALSE on cancel. The dialog destroys itself. */
|
/* Cancel/close authentication dialog */
|
||||||
/* Returns the popup widget so the caller can dismiss it externally
|
void wifi_auth_dialog_cancel(void);
|
||||||
* (e.g. on Agent.Cancel from iwd). The widget self-deletes on user
|
|
||||||
* action; treat the returned pointer as a weak reference. */
|
|
||||||
Evas_Object *wifi_auth_prompt(Evas_Object *parent, const char *ssid,
|
|
||||||
const char *security,
|
|
||||||
Wifi_Auth_Cb cb, void *data);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,115 +1,190 @@
|
||||||
#include "wifi_hidden.h"
|
#include "../e_mod_main.h"
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
typedef struct _Hidden_Ctx
|
/* Hidden network dialog structure */
|
||||||
|
typedef struct _Hidden_Dialog
|
||||||
{
|
{
|
||||||
Evas_Object *win;
|
Instance *inst;
|
||||||
Evas_Object *popup;
|
E_Dialog *dialog;
|
||||||
Evas_Object *e_ssid;
|
Evas_Object *ssid_entry;
|
||||||
Evas_Object *e_pass;
|
Evas_Object *pass_entry;
|
||||||
Wifi_Hidden_Cb cb;
|
char *ssid;
|
||||||
void *data;
|
char *passphrase;
|
||||||
Eina_Bool fired;
|
Eina_Bool has_password;
|
||||||
} Hidden_Ctx;
|
} Hidden_Dialog;
|
||||||
|
|
||||||
static void
|
/* Global hidden dialog */
|
||||||
_finish(Hidden_Ctx *c, Eina_Bool ok)
|
static Hidden_Dialog *hidden_dialog = NULL;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
/* Forward declarations */
|
||||||
_on_ok(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
static void _hidden_dialog_ok_cb(void *data, E_Dialog *dialog);
|
||||||
{
|
static void _hidden_dialog_cancel_cb(void *data, E_Dialog *dialog);
|
||||||
Hidden_Ctx *c = data;
|
static void _hidden_dialog_free(Hidden_Dialog *hd);
|
||||||
const char *ssid = elm_entry_entry_get(c->e_ssid);
|
|
||||||
if (!ssid || !*ssid) return; /* require non-empty SSID */
|
|
||||||
_finish(c, EINA_TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_on_cancel(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
|
||||||
{
|
|
||||||
_finish(data, EINA_FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_on_del(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
|
||||||
{
|
|
||||||
_finish(data, EINA_FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Evas_Object *
|
|
||||||
_labelled_entry(Evas_Object *box, const char *label_text, Eina_Bool password)
|
|
||||||
{
|
|
||||||
Evas_Object *lbl = elm_label_add(box);
|
|
||||||
elm_object_text_set(lbl, label_text);
|
|
||||||
evas_object_size_hint_align_set(lbl, 0.0, 0.5);
|
|
||||||
elm_box_pack_end(box, lbl);
|
|
||||||
evas_object_show(lbl);
|
|
||||||
|
|
||||||
Evas_Object *e = elm_entry_add(box);
|
|
||||||
elm_entry_single_line_set(e, EINA_TRUE);
|
|
||||||
elm_entry_scrollable_set(e, EINA_TRUE);
|
|
||||||
if (password) elm_entry_password_set(e, EINA_TRUE);
|
|
||||||
evas_object_size_hint_weight_set(e, EVAS_HINT_EXPAND, 0);
|
|
||||||
evas_object_size_hint_align_set(e, EVAS_HINT_FILL, 0);
|
|
||||||
elm_box_pack_end(box, e);
|
|
||||||
evas_object_show(e);
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Show hidden network dialog */
|
||||||
void
|
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));
|
Hidden_Dialog *hd;
|
||||||
c->cb = cb; c->data = data;
|
E_Dialog *dia;
|
||||||
|
Evas_Object *o, *list, *ssid_entry, *pass_entry;
|
||||||
|
|
||||||
/* Floating top-level so the popup actually shows. */
|
if (!inst) return;
|
||||||
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;
|
|
||||||
|
|
||||||
Evas_Object *bg = elm_bg_add(win);
|
/* Only one hidden dialog at a time */
|
||||||
evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
|
if (hidden_dialog)
|
||||||
elm_win_resize_object_add(win, bg);
|
{
|
||||||
evas_object_show(bg);
|
WRN("Hidden network dialog already open");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Evas_Object *p = elm_popup_add(win);
|
DBG("Showing hidden network dialog");
|
||||||
c->popup = p;
|
|
||||||
elm_object_part_text_set(p, "title,text", "Connect to hidden network");
|
|
||||||
|
|
||||||
Evas_Object *box = elm_box_add(p);
|
hd = E_NEW(Hidden_Dialog, 1);
|
||||||
elm_box_padding_set(box, 0, 4);
|
if (!hd) return;
|
||||||
|
|
||||||
c->e_ssid = _labelled_entry(box, "SSID:", EINA_FALSE);
|
hd->inst = inst;
|
||||||
c->e_pass = _labelled_entry(box, "Passphrase (optional):", EINA_TRUE);
|
hidden_dialog = hd;
|
||||||
|
|
||||||
evas_object_show(box);
|
/* Create dialog */
|
||||||
elm_object_content_set(p, box);
|
dia = e_dialog_new(NULL, "E", "iwd_hidden_network");
|
||||||
|
if (!dia)
|
||||||
|
{
|
||||||
|
_hidden_dialog_free(hd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Evas_Object *bcancel = elm_button_add(p);
|
hd->dialog = dia;
|
||||||
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);
|
e_dialog_title_set(dia, "Connect to Hidden Network");
|
||||||
elm_object_text_set(bok, "Connect");
|
e_dialog_icon_set(dia, "network-wireless", 48);
|
||||||
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);
|
/* Create content list */
|
||||||
|
list = e_widget_list_add(evas_object_evas_get(dia->win), 0, 0);
|
||||||
|
|
||||||
evas_object_show(p);
|
/* SSID label and entry */
|
||||||
evas_object_show(win);
|
o = e_widget_label_add(evas_object_evas_get(dia->win), "Network Name (SSID):");
|
||||||
elm_object_focus_set(c->e_ssid, EINA_TRUE);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
#ifndef WIFI_HIDDEN_H
|
#ifndef WIFI_HIDDEN_H
|
||||||
#define 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 ""),
|
/* Forward declarations */
|
||||||
* or ok=EINA_FALSE on cancel. The dialog destroys itself. */
|
typedef struct _Instance Instance;
|
||||||
typedef void (*Wifi_Hidden_Cb)(void *data, const char *ssid,
|
|
||||||
const char *passphrase, Eina_Bool ok);
|
|
||||||
|
|
||||||
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
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -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 */ }
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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 */ }
|
|
||||||
|
|
@ -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
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue