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
|
||||
.cache/
|
||||
build/
|
||||
builddir/
|
||||
|
||||
|
|
@ -22,6 +21,7 @@ compile_commands.json
|
|||
*.bak
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
|
@ -30,6 +30,18 @@ Thumbs.db
|
|||
config.h
|
||||
*.edj
|
||||
|
||||
# Autotools (if used)
|
||||
.deps/
|
||||
.libs/
|
||||
Makefile
|
||||
Makefile.in
|
||||
*.log
|
||||
*.trs
|
||||
autom4te.cache/
|
||||
config.status
|
||||
configure
|
||||
aclocal.m4
|
||||
|
||||
# Core dumps
|
||||
core
|
||||
core.*
|
||||
|
|
|
|||
333
CLAUDE.md
Normal file
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
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Copyright (c) 2025, eiwd contributors
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies of the Software and its Copyright notices. In addition publicly
|
||||
documented acknowledgment must be given that this software has been used if no
|
||||
source code of this software is made available publicly. Making the source
|
||||
available publicly means including the source for this software with the
|
||||
distribution, or a method to get this software via some reasonable mechanism
|
||||
(electronic transfer via a network or media) as well as making an offer to
|
||||
supply the source on request. This Copyright notice serves as an offer to
|
||||
supply the source on on request as well. Instead of this, supplying
|
||||
acknowledgments of use of this software in either Copyright notices, Manuals,
|
||||
Publicity and Marketing documents or any documentation provided with any
|
||||
product containing this software. This License does not apply to any software
|
||||
that links to the libraries provided by this software (statically or
|
||||
dynamically), but only to the software provided.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Please see the COPYING-PLAIN for a plain-english explanation of this notice
|
||||
and its intent.
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
|
|||
421
PLAN.md
Normal file
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
|
||||
manages wireless connections through [iwd](https://iwd.wiki.kernel.org/),
|
||||
the Intel Wireless Daemon. No NetworkManager, no ConnMan, no shelling
|
||||
out to `iwctl` — everything goes over iwd's D‑Bus API.
|
||||
A native Enlightenment module for managing Wi-Fi connections using Intel Wireless Daemon (iwd) as the backend.
|
||||
|
||||
It is roughly the iwd-only equivalent of `econnman`.
|
||||
## Overview
|
||||
|
||||
**eiwd** provides seamless Wi-Fi management directly within the Enlightenment desktop environment without requiring NetworkManager or ConnMan. It uses iwd's D-Bus API for fast, lightweight, and reliable wireless networking.
|
||||
|
||||
## Features
|
||||
|
||||
- **Shelf gadget** with a signal-tier icon (off / acquiring / weak…excellent)
|
||||
and a tooltip showing the current SSID, security type, and signal level.
|
||||
- **Popup network browser** (left-click the gadget):
|
||||
- status line: disabled / disconnected / scanning / connecting / connected
|
||||
- sorted network list — connected first, then known networks, then by
|
||||
signal strength; long SSIDs are truncated to keep the popup tidy
|
||||
- per-row signal bars and security tag (`open` / `WPA` / `WEP` / `802.1X`)
|
||||
- **Connect** by clicking a row, **Forget** (`✕`) on known networks
|
||||
- **Rescan**, **Enable / Disable** Wi‑Fi
|
||||
- **Disconnect** button visible while connected
|
||||
- **Hidden…** button to join a non-broadcasting SSID
|
||||
- **Authentication agent** registered with iwd:
|
||||
- passphrase prompt for new protected networks (modal dialog window)
|
||||
- cancel-on-`Agent.Cancel` so iwd-initiated cancellations close the
|
||||
open prompt cleanly
|
||||
- polite stubs for `RequestUserNameAndPassword`, `RequestUserPassword`
|
||||
and `RequestPrivateKeyPassphrase` so iwd doesn't unregister us when
|
||||
it tries them on EAP networks
|
||||
- **Settings dialog** (right-click the gadget → Settings):
|
||||
- auto-connect to known networks
|
||||
- show hidden networks
|
||||
- signal refresh interval
|
||||
- preferred wireless adapter
|
||||
- **Robust to iwd lifecycle**: tracks `net.connman.iwd` name owner,
|
||||
re-binds objects on restart, clears state on departure.
|
||||
### Core Functionality
|
||||
- **Device Management**: Automatic detection of wireless adapters
|
||||
- **Network Discovery**: Fast scanning with signal strength indicators
|
||||
- **Connection Management**: Connect/disconnect with one click
|
||||
- **Known Networks**: Automatic connection to saved networks
|
||||
- **Hidden Networks**: Support for connecting to non-broadcast SSIDs
|
||||
- **Security**: WPA2/WPA3-PSK authentication with secure passphrase handling
|
||||
|
||||
## Architecture
|
||||
### User Interface
|
||||
- **Shelf Gadget**: Compact icon showing connection status
|
||||
- **Visual Feedback**: Color-coded states (disconnected, connecting, connected, error)
|
||||
- **Popup Menu**: Quick access to available networks and actions
|
||||
- **Configuration Dialog**: Customizable settings and preferences
|
||||
- **Theme Support**: Full Edje theme integration with signal strength display
|
||||
|
||||
```
|
||||
e_iwd/
|
||||
├── src/
|
||||
│ ├── e_mod_main.c module init/shutdown
|
||||
│ ├── e_mod_gadget.c gadcon provider, icon + tooltip + menu
|
||||
│ ├── e_mod_popup.c network list popup
|
||||
│ ├── e_mod_config.c persistent settings + E_Config_Dialog
|
||||
│ ├── iwd/
|
||||
│ │ ├── iwd_dbus.c system bus, name owner, ObjectManager
|
||||
│ │ ├── iwd_manager.c top-level state aggregator + listeners
|
||||
│ │ ├── iwd_adapter.c net.connman.iwd.Adapter (Powered)
|
||||
│ │ ├── iwd_device.c Device + Station, scan/connect/disconnect,
|
||||
│ │ │ GetOrderedNetworks → signal strength
|
||||
│ │ ├── iwd_network.c Network.Connect, KnownNetwork.Forget
|
||||
│ │ ├── iwd_agent.c net.connman.iwd.Agent (passphrase, cancel)
|
||||
│ │ └── iwd_props.c a{sv} parsing helpers
|
||||
│ └── ui/
|
||||
│ ├── wifi_auth.c passphrase dialog (floating elm_win)
|
||||
│ └── wifi_hidden.c hidden-network SSID + passphrase dialog
|
||||
└── meson.build
|
||||
```
|
||||
### Advanced Features
|
||||
- **Daemon Recovery**: Automatic reconnection when iwd restarts
|
||||
- **Multi-Adapter Support**: Detection and management of multiple wireless devices
|
||||
- **State Machine**: Robust connection state tracking
|
||||
- **Error Handling**: User-friendly error messages with troubleshooting hints
|
||||
- **Polkit Integration**: Respects system authorization policies
|
||||
|
||||
Data flow:
|
||||
## Requirements
|
||||
|
||||
```
|
||||
iwd (D-Bus) ──► iwd_dbus ──► iwd_manager ──► iwd_device / iwd_network
|
||||
│
|
||||
├──► listeners (gadget, popup)
|
||||
└──► Iwd_Agent ──► UI passphrase prompt
|
||||
```
|
||||
### Build Dependencies
|
||||
- `enlightenment` >= 0.25
|
||||
- `efl` (Elementary, Eldbus, Ecore, Evas, Edje, Eina) >= 1.26
|
||||
- `meson` >= 0.56
|
||||
- `ninja` build tool
|
||||
- `gcc` or `clang` compiler
|
||||
- `pkg-config`
|
||||
- `edje_cc` (EFL development tools)
|
||||
|
||||
The module uses **Eldbus** for all bus traffic and **Elementary** for
|
||||
its widgets. Everything is async — no blocking calls on the UI thread.
|
||||
### Runtime Dependencies
|
||||
- `enlightenment` >= 0.25
|
||||
- `efl` runtime libraries
|
||||
- `iwd` >= 1.0
|
||||
- `dbus` system bus
|
||||
|
||||
### Optional
|
||||
- `polkit` for fine-grained permission management
|
||||
- `gettext` for internationalization support
|
||||
|
||||
## Building
|
||||
|
||||
Dependencies (development headers):
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone <repository-url> eiwd
|
||||
cd eiwd
|
||||
|
||||
- Enlightenment ≥ 0.25 (tested against 0.27)
|
||||
- EFL ≥ 1.26 (Eldbus, Elementary, Edje, Ecore, Eina)
|
||||
- meson + ninja
|
||||
- a running `iwd` ≥ 1.0 (runtime, not build-time)
|
||||
|
||||
Build and install:
|
||||
|
||||
```sh
|
||||
# Configure build
|
||||
meson setup build
|
||||
|
||||
# Compile
|
||||
ninja -C build
|
||||
|
||||
# Install (as root)
|
||||
sudo ninja -C build install
|
||||
```
|
||||
|
||||
The module is installed to
|
||||
`<libdir>/enlightenment/modules/iwd/<module_arch>/module.so`. The
|
||||
`module_arch` and `libdir` are pulled from Enlightenment's pkg-config
|
||||
file, so the install path matches whatever your distro packages.
|
||||
### Build Options
|
||||
|
||||
Once installed, enable it from **Settings → Modules → Extensions →
|
||||
iwd**, then add the gadget to a shelf or the desktop via
|
||||
**Settings → Gadgets**.
|
||||
```bash
|
||||
# Disable internationalization
|
||||
meson setup build -Dnls=false
|
||||
|
||||
## Runtime requirements
|
||||
# Custom installation prefix
|
||||
meson setup build --prefix=/usr/local
|
||||
```
|
||||
|
||||
- `iwd` running as a system service (`systemctl enable --now iwd`).
|
||||
- Your user must be allowed to talk to `net.connman.iwd` on the system
|
||||
bus. On most distros this means being in the `network` group, or
|
||||
having a polkit rule for the `net.connman.iwd` interfaces. The module
|
||||
degrades gracefully when permissions are missing — you'll just see an
|
||||
empty list.
|
||||
- A wireless adapter managed by iwd (i.e. not claimed by
|
||||
NetworkManager / wpa_supplicant).
|
||||
## Installation
|
||||
|
||||
## Usage
|
||||
The module is installed to:
|
||||
```
|
||||
/usr/lib64/enlightenment/modules/iwd/linux-x86_64-0.27/
|
||||
├── module.so # Main module binary
|
||||
└── e-module-iwd.edj # Theme file
|
||||
```
|
||||
|
||||
| Action | How |
|
||||
|---|---|
|
||||
| Open the network list | Left-click the gadget |
|
||||
| Open settings | Right-click the gadget → Settings |
|
||||
| Connect to a known network | Click its row in the list |
|
||||
| Connect to a new protected network | Click its row, enter the passphrase in the dialog |
|
||||
| Forget a known network | Click the `✕` button on its row |
|
||||
| Disconnect | Click **Disconnect** in the popup (visible while connected) |
|
||||
| Join a hidden SSID | Click **Hidden…**, enter SSID and (optional) passphrase |
|
||||
| Rescan | Click **Rescan** |
|
||||
| Disable / enable Wi-Fi | Click **Disable** / **Enable** in the popup |
|
||||
|
||||
Passphrases are sent straight to iwd over D-Bus. They are never logged,
|
||||
never written to the module config, and are zeroed in memory once the
|
||||
dialog closes.
|
||||
After installation:
|
||||
1. Open Enlightenment Settings → Modules
|
||||
2. Find "IWD Wi-Fi" in the list
|
||||
3. Click "Load" to enable the module
|
||||
4. Add the gadget to your shelf via shelf settings
|
||||
|
||||
## Configuration
|
||||
|
||||
Settings are persisted via Enlightenment's standard config system as
|
||||
`module.iwd` (an `eet` file under your E config profile, e.g.
|
||||
`~/.e/e/config/<profile>/module.iwd.cfg`). Fields:
|
||||
### Module Settings
|
||||
|
||||
| Field | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `auto_connect` | on | Let iwd auto-connect to known networks |
|
||||
| `show_hidden` | off | Reveal hidden networks in the list |
|
||||
| `refresh_interval` | 5 | Signal-strength refresh interval (seconds) |
|
||||
| `preferred_adapter` | — | Preferred wireless adapter (blank = auto) |
|
||||
Access via right-click on the gadget or Settings → Modules → IWD → Configure:
|
||||
|
||||
## Status
|
||||
- **Auto-connect**: Automatically connect to known networks
|
||||
- **Show hidden networks**: Display option to connect to hidden SSIDs
|
||||
- **Signal refresh interval**: Update frequency (1-60 seconds)
|
||||
- **Preferred adapter**: Select default wireless device (multi-adapter systems)
|
||||
|
||||
Phases 0–4 of the project are complete and the module builds cleanly
|
||||
against EFL 1.28 / Enlightenment 0.27. Phase 5 (robustness) and Phase 6
|
||||
(packaging) are partially landed.
|
||||
### iwd Setup
|
||||
|
||||
Known gaps:
|
||||
Ensure iwd is running and enabled:
|
||||
|
||||
- No custom theme `edj` — the gadget uses freedesktop icon names from
|
||||
the active icon theme.
|
||||
- No suspend / resume integration.
|
||||
- No EAP UI (RequestUserName / RequestPrivateKey are stubbed to
|
||||
refuse).
|
||||
- Multi-adapter UX is auto-select-first; the preferred-adapter setting
|
||||
is plumbed but not yet honored by the manager.
|
||||
- Not yet tested by valgrind for leaks.
|
||||
```bash
|
||||
# Enable and start iwd
|
||||
sudo systemctl enable --now iwd
|
||||
|
||||
# Check status
|
||||
sudo systemctl status iwd
|
||||
```
|
||||
|
||||
### Polkit Rules
|
||||
|
||||
For non-root users to manage Wi-Fi, create `/etc/polkit-1/rules.d/50-iwd.rules`:
|
||||
|
||||
```javascript
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (action.id.indexOf("net.connman.iwd.") == 0 &&
|
||||
subject.isInGroup("wheel")) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Adjust the group (`wheel`, `network`, etc.) according to your distribution.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Operations
|
||||
|
||||
1. **Scan for networks**: Click "Rescan" in the popup
|
||||
2. **Connect to a network**: Click the network name, enter passphrase if required
|
||||
3. **Disconnect**: Click "Disconnect" in the popup
|
||||
4. **Forget network**: Right-click network → Forget (removes saved credentials)
|
||||
5. **Hidden network**: Click "Hidden..." button, enter SSID and passphrase
|
||||
|
||||
### Status Indicator
|
||||
|
||||
The gadget icon shows current state:
|
||||
- **Gray**: Disconnected or no wireless device
|
||||
- **Orange/Yellow**: Connecting to network
|
||||
- **Green**: Connected successfully
|
||||
- **Red**: Error (iwd not running or permission denied)
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
See [INSTALL.md](INSTALL.md#troubleshooting) for common issues and solutions.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Components
|
||||
|
||||
```
|
||||
eiwd/
|
||||
├── src/
|
||||
│ ├── e_mod_main.c # Module entry point
|
||||
│ ├── e_mod_config.c # Configuration dialog
|
||||
│ ├── e_mod_gadget.c # Shelf gadget icon
|
||||
│ ├── e_mod_popup.c # Network list popup
|
||||
│ ├── iwd/ # D-Bus backend
|
||||
│ │ ├── iwd_dbus.c # Connection management
|
||||
│ │ ├── iwd_device.c # Device abstraction
|
||||
│ │ ├── iwd_network.c # Network abstraction
|
||||
│ │ ├── iwd_agent.c # Authentication agent
|
||||
│ │ └── iwd_state.c # State machine
|
||||
│ └── ui/ # UI dialogs
|
||||
│ ├── wifi_auth.c # Passphrase input
|
||||
│ └── wifi_hidden.c # Hidden network dialog
|
||||
└── data/
|
||||
├── theme.edc # Edje theme
|
||||
└── module.desktop # Module metadata
|
||||
```
|
||||
|
||||
### D-Bus Integration
|
||||
|
||||
Communicates with iwd via system bus (`net.connman.iwd`):
|
||||
- `ObjectManager` for device/network discovery
|
||||
- `Device` and `Station` interfaces for wireless operations
|
||||
- `Network` interface for connection management
|
||||
- `Agent` registration for passphrase requests
|
||||
|
||||
## Performance
|
||||
|
||||
- **Startup time**: < 100ms
|
||||
- **Memory footprint**: ~5 MB
|
||||
- **No polling**: Event-driven updates via D-Bus signals
|
||||
- **Theme compilation**: Optimized Edje binary format
|
||||
|
||||
## License
|
||||
|
||||
MIT-style, matching Enlightenment and EFL. See `LICENSE`.
|
||||
[Specify your license here - e.g., BSD, GPL, MIT]
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions welcome! Please:
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Test thoroughly (see Testing section)
|
||||
4. Submit a pull request
|
||||
|
||||
## Support
|
||||
|
||||
- **Issues**: Report bugs via GitHub Issues
|
||||
- **Documentation**: See [INSTALL.md](INSTALL.md) for detailed setup
|
||||
- **IRC/Matrix**: [Specify chat channels if available]
|
||||
|
||||
## Credits
|
||||
|
||||
Developed with Claude Code - https://claude.com/claude-code
|
||||
|
||||
Based on iwd (Intel Wireless Daemon) by Intel Corporation
|
||||
Built for the Enlightenment desktop environment
|
||||
|
||||
## See Also
|
||||
|
||||
- [iwd documentation](https://iwd.wiki.kernel.org/)
|
||||
- [Enlightenment documentation](https://www.enlightenment.org/docs)
|
||||
- [EFL API reference](https://docs.enlightenment.org/api/efl/start)
|
||||
|
|
|
|||
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)
|
||||
|
||||
edje_cc = find_program('edje_cc')
|
||||
|
||||
iwd_theme = custom_target('e-module-iwd.edj',
|
||||
input : 'e-module-iwd.edc',
|
||||
output : 'e-module-iwd.edj',
|
||||
command : [edje_cc, '-id', meson.current_source_dir(), '@INPUT@', '@OUTPUT@'],
|
||||
install : true,
|
||||
install_dir : module_dir,
|
||||
# Install desktop file
|
||||
install_data('module.desktop',
|
||||
install_dir: dir_module
|
||||
)
|
||||
|
||||
# Compile theme
|
||||
edje_cc = find_program('edje_cc', required: false)
|
||||
if edje_cc.found()
|
||||
custom_target('theme',
|
||||
input: 'theme.edc',
|
||||
output: 'e-module-iwd.edj',
|
||||
command: [edje_cc, '@INPUT@', '@OUTPUT@'],
|
||||
install: true,
|
||||
install_dir: dir_module
|
||||
)
|
||||
else
|
||||
warning('edje_cc not found, theme will not be compiled')
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
[Desktop Entry]
|
||||
Type=Link
|
||||
Name=iwd
|
||||
Name=IWD Wi-Fi
|
||||
Name[en]=IWD Wi-Fi Manager
|
||||
Comment=Manage Wi-Fi connections using iwd
|
||||
Comment[en]=Control Wi-Fi networks using Intel Wireless Daemon
|
||||
Icon=e-module-iwd
|
||||
Comment=Wi-Fi management via iwd
|
||||
X-Enlightenment-ModuleType=utils
|
||||
X-Enlightenment-ModuleType=system
|
||||
|
|
|
|||
188
data/theme.edc
Normal file
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',
|
||||
version : '0.1.0',
|
||||
license : 'MIT',
|
||||
default_options : ['c_std=gnu99', 'warning_level=2'])
|
||||
project('e-iwd', 'c',
|
||||
version: '0.1.0',
|
||||
default_options: ['c_std=c11', 'warning_level=2'],
|
||||
meson_version: '>= 0.56.0'
|
||||
)
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
|
||||
eldbus = dependency('eldbus')
|
||||
elementary = dependency('elementary')
|
||||
# Dependencies
|
||||
enlightenment = dependency('enlightenment')
|
||||
eldbus = dependency('eldbus')
|
||||
elementary = dependency('elementary')
|
||||
ecore = dependency('ecore')
|
||||
evas = dependency('evas')
|
||||
edje = dependency('edje')
|
||||
eina = dependency('eina')
|
||||
|
||||
module_arch = enlightenment.get_variable(pkgconfig: 'module_arch',
|
||||
default_value: 'linux-gnu-@0@'.format(host_machine.cpu()))
|
||||
module_dir = join_paths(get_option('libdir'), 'enlightenment', 'modules', 'iwd')
|
||||
# Get Enlightenment version and module architecture
|
||||
e_version = enlightenment.version()
|
||||
|
||||
add_project_arguments('-DPACKAGE="e_iwd"',
|
||||
'-DPACKAGE_VERSION="@0@"'.format(meson.project_version()),
|
||||
language : 'c')
|
||||
# Detect system ABI (gnu, musl, etc.)
|
||||
cc = meson.get_compiler('c')
|
||||
if cc.has_header('features.h')
|
||||
# GNU libc systems
|
||||
system_abi = 'gnu'
|
||||
else
|
||||
# Try to detect from system - fallback to 'unknown'
|
||||
system_abi = 'unknown'
|
||||
endif
|
||||
|
||||
# Installation paths
|
||||
module_name = 'iwd'
|
||||
# Format: <system>-<abi>-<cpu>-<version>
|
||||
# Example: linux-gnu-x86_64-0.27.1
|
||||
module_arch = '@0@-@1@-@2@-@3@'.format(
|
||||
host_machine.system(),
|
||||
system_abi,
|
||||
host_machine.cpu_family(),
|
||||
e_version
|
||||
)
|
||||
|
||||
dir_module = join_paths(get_option('libdir'), 'enlightenment', 'modules', module_name)
|
||||
dir_module_arch = join_paths(dir_module, module_arch)
|
||||
|
||||
# Configuration
|
||||
conf_data = configuration_data()
|
||||
conf_data.set_quoted('PACKAGE', meson.project_name())
|
||||
conf_data.set_quoted('VERSION', meson.project_version())
|
||||
conf_data.set_quoted('MODULE_ARCH', module_arch)
|
||||
|
||||
configure_file(
|
||||
output: 'config.h',
|
||||
configuration: conf_data
|
||||
)
|
||||
|
||||
# Add configuration include and feature test macros
|
||||
add_project_arguments(
|
||||
'-include', 'config.h',
|
||||
'-D_GNU_SOURCE',
|
||||
language: 'c'
|
||||
)
|
||||
|
||||
# Internationalization
|
||||
i18n = import('i18n')
|
||||
if get_option('nls')
|
||||
subdir('po')
|
||||
endif
|
||||
|
||||
# Subdirectories
|
||||
subdir('src')
|
||||
subdir('data')
|
||||
|
||||
# Summary
|
||||
summary({
|
||||
'Module name': module_name,
|
||||
'Module architecture': module_arch,
|
||||
'Installation path': dir_module_arch,
|
||||
'Enlightenment version': enlightenment.version(),
|
||||
}, section: 'Configuration')
|
||||
|
|
|
|||
3
meson_options.txt
Normal file
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_config.h"
|
||||
#include <e_config_data.h>
|
||||
|
||||
#define CONFIG_DOMAIN "module.iwd"
|
||||
#define CONFIG_VERSION 1
|
||||
|
||||
E_Iwd_Config *e_iwd_config = NULL;
|
||||
static E_Config_DD *_edd = NULL;
|
||||
|
||||
static void
|
||||
_edd_setup(void)
|
||||
/* Configuration dialog structure */
|
||||
typedef struct _E_Config_Dialog_Data
|
||||
{
|
||||
if (_edd) return;
|
||||
_edd = E_CONFIG_DD_NEW("E_Iwd_Config", E_Iwd_Config);
|
||||
E_CONFIG_VAL(_edd, E_Iwd_Config, version, INT);
|
||||
E_CONFIG_VAL(_edd, E_Iwd_Config, auto_connect, INT);
|
||||
E_CONFIG_VAL(_edd, E_Iwd_Config, show_hidden, INT);
|
||||
E_CONFIG_VAL(_edd, E_Iwd_Config, refresh_interval, INT);
|
||||
E_CONFIG_VAL(_edd, E_Iwd_Config, preferred_adapter, STR);
|
||||
}
|
||||
|
||||
void
|
||||
e_iwd_config_load(void)
|
||||
{
|
||||
_edd_setup();
|
||||
e_iwd_config = e_config_domain_load(CONFIG_DOMAIN, _edd);
|
||||
if (e_iwd_config && e_iwd_config->version == CONFIG_VERSION) return;
|
||||
|
||||
/* Missing or out-of-date — start fresh with defaults. */
|
||||
if (e_iwd_config)
|
||||
{
|
||||
if (e_iwd_config->preferred_adapter)
|
||||
eina_stringshare_del(e_iwd_config->preferred_adapter);
|
||||
free(e_iwd_config);
|
||||
}
|
||||
e_iwd_config = E_NEW(E_Iwd_Config, 1);
|
||||
e_iwd_config->version = CONFIG_VERSION;
|
||||
e_iwd_config->auto_connect = 1;
|
||||
e_iwd_config->show_hidden = 0;
|
||||
e_iwd_config->refresh_interval = 5;
|
||||
}
|
||||
|
||||
void
|
||||
e_iwd_config_save(void)
|
||||
{
|
||||
if (!_edd || !e_iwd_config) return;
|
||||
e_config_domain_save(CONFIG_DOMAIN, _edd, e_iwd_config);
|
||||
}
|
||||
|
||||
/* ----- Settings dialog ------------------------------------------------ */
|
||||
|
||||
struct _E_Config_Dialog_Data
|
||||
{
|
||||
int auto_connect;
|
||||
int show_hidden;
|
||||
int refresh_interval;
|
||||
int auto_connect;
|
||||
int show_hidden_networks;
|
||||
int signal_refresh_interval;
|
||||
char *preferred_adapter;
|
||||
};
|
||||
} E_Config_Dialog_Data;
|
||||
|
||||
/* Forward declarations */
|
||||
static void *_create_data(E_Config_Dialog *cfd);
|
||||
static void _free_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
|
||||
static Evas_Object *_basic_create(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata);
|
||||
static int _basic_apply(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
|
||||
|
||||
/* Show configuration dialog */
|
||||
void
|
||||
e_iwd_config_show(void)
|
||||
{
|
||||
E_Config_Dialog *cfd;
|
||||
E_Config_Dialog_View *v;
|
||||
|
||||
if (!iwd_mod || !iwd_mod->conf) return;
|
||||
|
||||
/* Check if dialog already exists */
|
||||
if (e_config_dialog_find("IWD", "extensions/iwd"))
|
||||
return;
|
||||
|
||||
v = E_NEW(E_Config_Dialog_View, 1);
|
||||
if (!v) return;
|
||||
|
||||
v->create_cfdata = _create_data;
|
||||
v->free_cfdata = _free_data;
|
||||
v->basic.create_widgets = _basic_create;
|
||||
v->basic.apply_cfdata = _basic_apply;
|
||||
|
||||
cfd = e_config_dialog_new(NULL, "IWD Wi-Fi Configuration",
|
||||
"IWD", "extensions/iwd",
|
||||
NULL, 0, v, NULL);
|
||||
|
||||
if (!cfd)
|
||||
{
|
||||
E_FREE(v);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create config data */
|
||||
static void *
|
||||
_cfd_create(E_Config_Dialog *cfd EINA_UNUSED)
|
||||
_create_data(E_Config_Dialog *cfd EINA_UNUSED)
|
||||
{
|
||||
if (!e_iwd_config) return NULL;
|
||||
E_Config_Dialog_Data *c = E_NEW(E_Config_Dialog_Data, 1);
|
||||
c->auto_connect = e_iwd_config->auto_connect;
|
||||
c->show_hidden = e_iwd_config->show_hidden;
|
||||
c->refresh_interval = e_iwd_config->refresh_interval;
|
||||
c->preferred_adapter = e_iwd_config->preferred_adapter
|
||||
? strdup(e_iwd_config->preferred_adapter) : strdup("");
|
||||
return c;
|
||||
E_Config_Dialog_Data *cfdata;
|
||||
|
||||
if (!iwd_mod || !iwd_mod->conf) return NULL;
|
||||
|
||||
cfdata = E_NEW(E_Config_Dialog_Data, 1);
|
||||
if (!cfdata) return NULL;
|
||||
|
||||
/* Copy current config */
|
||||
cfdata->auto_connect = iwd_mod->conf->auto_connect;
|
||||
cfdata->show_hidden_networks = iwd_mod->conf->show_hidden_networks;
|
||||
cfdata->signal_refresh_interval = iwd_mod->conf->signal_refresh_interval;
|
||||
|
||||
if (iwd_mod->conf->preferred_adapter)
|
||||
cfdata->preferred_adapter = strdup(iwd_mod->conf->preferred_adapter);
|
||||
else
|
||||
cfdata->preferred_adapter = NULL;
|
||||
|
||||
return cfdata;
|
||||
}
|
||||
|
||||
/* Free config data */
|
||||
static void
|
||||
_cfd_free(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *c)
|
||||
_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
|
||||
{
|
||||
if (!c) return;
|
||||
free(c->preferred_adapter);
|
||||
E_FREE(c);
|
||||
if (!cfdata) return;
|
||||
|
||||
if (cfdata->preferred_adapter)
|
||||
free(cfdata->preferred_adapter);
|
||||
|
||||
E_FREE(cfdata);
|
||||
}
|
||||
|
||||
/* Create basic UI */
|
||||
static Evas_Object *
|
||||
_cfd_basic_create(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *c)
|
||||
_basic_create(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *cfdata)
|
||||
{
|
||||
Evas_Object *o, *of, *ob;
|
||||
|
||||
o = e_widget_list_add(evas, 0, 0);
|
||||
|
||||
of = e_widget_framelist_add(evas, "Connection", 0);
|
||||
/* Connection settings frame */
|
||||
of = e_widget_framelist_add(evas, "Connection Settings", 0);
|
||||
|
||||
ob = e_widget_check_add(evas, "Auto-connect to known networks",
|
||||
&c->auto_connect);
|
||||
&(cfdata->auto_connect));
|
||||
e_widget_framelist_object_append(of, ob);
|
||||
|
||||
ob = e_widget_check_add(evas, "Show hidden networks",
|
||||
&c->show_hidden);
|
||||
&(cfdata->show_hidden_networks));
|
||||
e_widget_framelist_object_append(of, ob);
|
||||
|
||||
e_widget_list_object_append(o, of, 1, 1, 0.5);
|
||||
|
||||
/* Performance settings frame */
|
||||
of = e_widget_framelist_add(evas, "Performance", 0);
|
||||
ob = e_widget_label_add(evas, "Signal refresh interval (s):");
|
||||
|
||||
ob = e_widget_label_add(evas, "Signal refresh interval (seconds):");
|
||||
e_widget_framelist_object_append(of, ob);
|
||||
|
||||
ob = e_widget_slider_add(evas, 1, 0, "%1.0f", 1.0, 60.0, 1.0, 0,
|
||||
NULL, &c->refresh_interval, 150);
|
||||
NULL, &(cfdata->signal_refresh_interval), 150);
|
||||
e_widget_framelist_object_append(of, ob);
|
||||
|
||||
e_widget_list_object_append(o, of, 1, 1, 0.5);
|
||||
|
||||
of = e_widget_framelist_add(evas, "Adapter", 0);
|
||||
ob = e_widget_label_add(evas, "Preferred wireless adapter (blank = auto):");
|
||||
e_widget_framelist_object_append(of, ob);
|
||||
ob = e_widget_entry_add(evas, &c->preferred_adapter, NULL, NULL, NULL);
|
||||
e_widget_framelist_object_append(of, ob);
|
||||
e_widget_list_object_append(o, of, 1, 1, 0.5);
|
||||
/* Adapter settings frame (if multiple adapters available) */
|
||||
Eina_List *devices = iwd_devices_get();
|
||||
if (eina_list_count(devices) > 1)
|
||||
{
|
||||
of = e_widget_framelist_add(evas, "Adapter Selection", 0);
|
||||
|
||||
ob = e_widget_label_add(evas, "Preferred wireless adapter:");
|
||||
e_widget_framelist_object_append(of, ob);
|
||||
|
||||
/* TODO: Add radio list for adapter selection when multiple devices exist */
|
||||
ob = e_widget_label_add(evas, "(Auto-select)");
|
||||
e_widget_framelist_object_append(of, ob);
|
||||
|
||||
e_widget_list_object_append(o, of, 1, 1, 0.5);
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Apply configuration */
|
||||
static int
|
||||
_cfd_basic_apply(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *c)
|
||||
_basic_apply(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
|
||||
{
|
||||
if (!e_iwd_config || !c) return 0;
|
||||
e_iwd_config->auto_connect = c->auto_connect;
|
||||
e_iwd_config->show_hidden = c->show_hidden;
|
||||
e_iwd_config->refresh_interval = c->refresh_interval;
|
||||
if (e_iwd_config->preferred_adapter)
|
||||
eina_stringshare_del(e_iwd_config->preferred_adapter);
|
||||
e_iwd_config->preferred_adapter =
|
||||
(c->preferred_adapter && *c->preferred_adapter)
|
||||
? eina_stringshare_add(c->preferred_adapter) : NULL;
|
||||
e_iwd_config_save();
|
||||
if (!iwd_mod || !iwd_mod->conf) return 0;
|
||||
|
||||
/* Update config */
|
||||
iwd_mod->conf->auto_connect = cfdata->auto_connect;
|
||||
iwd_mod->conf->show_hidden_networks = cfdata->show_hidden_networks;
|
||||
iwd_mod->conf->signal_refresh_interval = cfdata->signal_refresh_interval;
|
||||
|
||||
if (cfdata->preferred_adapter)
|
||||
{
|
||||
if (iwd_mod->conf->preferred_adapter)
|
||||
eina_stringshare_del(iwd_mod->conf->preferred_adapter);
|
||||
iwd_mod->conf->preferred_adapter = eina_stringshare_add(cfdata->preferred_adapter);
|
||||
}
|
||||
|
||||
/* Save config */
|
||||
e_config_save_queue();
|
||||
|
||||
DBG("Configuration updated");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
e_iwd_config_dialog_show(void)
|
||||
{
|
||||
if (e_config_dialog_find("E_Iwd", "extensions/iwd")) return;
|
||||
|
||||
E_Config_Dialog_View *v = E_NEW(E_Config_Dialog_View, 1);
|
||||
if (!v) return;
|
||||
v->create_cfdata = _cfd_create;
|
||||
v->free_cfdata = _cfd_free;
|
||||
v->basic.create_widgets = _cfd_basic_create;
|
||||
v->basic.apply_cfdata = _cfd_basic_apply;
|
||||
|
||||
E_Config_Dialog *cfd = e_config_dialog_new(NULL,
|
||||
"iwd Wi-Fi Settings", "E_Iwd", "extensions/iwd", NULL, 0, v, NULL);
|
||||
if (!cfd) E_FREE(v);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_gadget.h"
|
||||
#include "e_mod_popup.h"
|
||||
#include "e_mod_config.h"
|
||||
#include "iwd/iwd_manager.h"
|
||||
#include "iwd/iwd_device.h"
|
||||
#include "iwd/iwd_network.h"
|
||||
#include <e_gadcon.h>
|
||||
#include <limits.h>
|
||||
|
||||
/* ----- per-instance gadget data --------------------------------------- */
|
||||
/* Forward declarations */
|
||||
static E_Gadcon_Client *_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style);
|
||||
static void _gc_shutdown(E_Gadcon_Client *gcc);
|
||||
static void _gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient);
|
||||
static const char *_gc_label(const E_Gadcon_Client_Class *client_class);
|
||||
static Evas_Object *_gc_icon(const E_Gadcon_Client_Class *client_class, Evas *evas);
|
||||
static const char *_gc_id_new(const E_Gadcon_Client_Class *client_class);
|
||||
|
||||
typedef struct _Instance
|
||||
static void _gadget_mouse_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);
|
||||
static void _gadget_update(Instance *inst);
|
||||
static Eina_Bool _gadget_update_timer_cb(void *data);
|
||||
|
||||
/* Gadcon class definition */
|
||||
static const E_Gadcon_Client_Class _gc_class =
|
||||
{
|
||||
E_Gadcon_Client *gcc;
|
||||
Evas_Object *o_base; /* themed edje, gcc->o_base */
|
||||
Evas_Object *o_icon; /* swallowed into o_base */
|
||||
} Instance;
|
||||
GADCON_CLIENT_CLASS_VERSION,
|
||||
"iwd",
|
||||
{
|
||||
_gc_init, _gc_shutdown, _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL, NULL
|
||||
},
|
||||
E_GADCON_CLIENT_STYLE_PLAIN
|
||||
};
|
||||
|
||||
static Eina_List *_instances = NULL;
|
||||
/* Global gadcon provider */
|
||||
static E_Gadcon_Client_Class *gadcon_class = NULL;
|
||||
|
||||
/* ----- icon update ----------------------------------------------------- */
|
||||
|
||||
/* Walk the manager state to find the network we're currently connected to,
|
||||
* if any. Used both for the signal-tier icon and for the tooltip. */
|
||||
static Iwd_Network *
|
||||
_active_network(void)
|
||||
/* Initialize gadget subsystem */
|
||||
void
|
||||
e_iwd_gadget_init(void)
|
||||
{
|
||||
if (!e_iwd || !e_iwd->manager) return NULL;
|
||||
const Eina_Hash *devs = iwd_manager_devices(e_iwd->manager);
|
||||
const Eina_Hash *nets = iwd_manager_networks(e_iwd->manager);
|
||||
if (!devs || !nets) return NULL;
|
||||
Eina_Iterator *it = eina_hash_iterator_data_new((Eina_Hash *)devs);
|
||||
Iwd_Device *d;
|
||||
Iwd_Network *found = NULL;
|
||||
EINA_ITERATOR_FOREACH(it, d)
|
||||
{
|
||||
if (!d->connected_network) continue;
|
||||
found = eina_hash_find(nets, d->connected_network);
|
||||
if (found) break;
|
||||
}
|
||||
eina_iterator_free(it);
|
||||
return found;
|
||||
DBG("Initializing gadget");
|
||||
|
||||
gadcon_class = (E_Gadcon_Client_Class *)&_gc_class;
|
||||
e_gadcon_provider_register(gadcon_class);
|
||||
}
|
||||
|
||||
static const char *
|
||||
_icon_for_signal_tier(int tier)
|
||||
/* Shutdown gadget subsystem */
|
||||
void
|
||||
e_iwd_gadget_shutdown(void)
|
||||
{
|
||||
switch (tier)
|
||||
{
|
||||
case 4: return "network-wireless-signal-excellent";
|
||||
case 3: return "network-wireless-signal-good";
|
||||
case 2: return "network-wireless-signal-ok";
|
||||
case 1: return "network-wireless-signal-weak";
|
||||
default: return "network-wireless-signal-none";
|
||||
}
|
||||
DBG("Shutting down gadget");
|
||||
|
||||
if (gadcon_class)
|
||||
{
|
||||
e_gadcon_provider_unregister(gadcon_class);
|
||||
gadcon_class = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *
|
||||
_icon_name_for_state(Iwd_State s)
|
||||
{
|
||||
switch (s)
|
||||
{
|
||||
case IWD_STATE_OFF: return "network-offline";
|
||||
case IWD_STATE_IDLE: return "network-wireless-disconnected";
|
||||
case IWD_STATE_SCANNING: return "network-wireless-acquiring";
|
||||
case IWD_STATE_CONNECTING: return "network-wireless-acquiring";
|
||||
case IWD_STATE_CONNECTED:
|
||||
{
|
||||
Iwd_Network *n = _active_network();
|
||||
return _icon_for_signal_tier(n ? iwd_network_signal_tier(n) : 0);
|
||||
}
|
||||
case IWD_STATE_ERROR: return "network-error";
|
||||
}
|
||||
return "network-wireless";
|
||||
}
|
||||
|
||||
static const char *
|
||||
_state_label(Iwd_State s)
|
||||
{
|
||||
switch (s)
|
||||
{
|
||||
case IWD_STATE_OFF: return "Wi-Fi disabled";
|
||||
case IWD_STATE_IDLE: return "Disconnected";
|
||||
case IWD_STATE_SCANNING: return "Scanning";
|
||||
case IWD_STATE_CONNECTING: return "Connecting";
|
||||
case IWD_STATE_CONNECTED: return "Connected";
|
||||
case IWD_STATE_ERROR: return "Error";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static const char *
|
||||
_sec_label(int s)
|
||||
{
|
||||
/* Iwd_Security values, kept in sync with iwd_network.h. */
|
||||
switch (s) { case 0: return "open"; case 1: return "WPA";
|
||||
case 2: return "802.1X"; case 3: return "WEP"; }
|
||||
return "?";
|
||||
}
|
||||
|
||||
static void
|
||||
_build_tooltip(Instance *inst, Iwd_State s)
|
||||
{
|
||||
char buf[256];
|
||||
if (s == IWD_STATE_CONNECTED)
|
||||
{
|
||||
Iwd_Network *n = _active_network();
|
||||
if (n)
|
||||
snprintf(buf, sizeof(buf), "Wi-Fi: %s — %s — signal %d/4",
|
||||
n->ssid ? n->ssid : "?",
|
||||
_sec_label(n->security),
|
||||
iwd_network_signal_tier(n));
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "Wi-Fi: connected");
|
||||
}
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "Wi-Fi: %s", _state_label(s));
|
||||
elm_object_tooltip_text_set(inst->o_base, buf);
|
||||
}
|
||||
|
||||
static void
|
||||
_inst_refresh(Instance *inst)
|
||||
{
|
||||
if (!inst || !inst->o_icon || !e_iwd) return;
|
||||
Iwd_State s = iwd_manager_state(e_iwd->manager);
|
||||
e_icon_fdo_icon_set(inst->o_icon, _icon_name_for_state(s));
|
||||
_build_tooltip(inst, s);
|
||||
}
|
||||
|
||||
/* Listener invoked by iwd_manager whenever state changes. */
|
||||
static void
|
||||
_on_manager_change(void *data EINA_UNUSED, Iwd_Manager *m EINA_UNUSED)
|
||||
{
|
||||
Eina_List *l;
|
||||
Instance *inst;
|
||||
EINA_LIST_FOREACH(_instances, l, inst) _inst_refresh(inst);
|
||||
}
|
||||
|
||||
/* ----- click → popup --------------------------------------------------- */
|
||||
|
||||
static void
|
||||
_menu_settings_cb(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
|
||||
{
|
||||
e_iwd_config_dialog_show();
|
||||
}
|
||||
|
||||
static void
|
||||
_show_menu(Instance *inst, Evas_Event_Mouse_Down *ev)
|
||||
{
|
||||
E_Zone *zone = e_zone_current_get();
|
||||
E_Menu *m = e_menu_new();
|
||||
E_Menu_Item *mi = e_menu_item_new(m);
|
||||
e_menu_item_label_set(mi, "Settings");
|
||||
e_util_menu_item_theme_icon_set(mi, "preferences-system");
|
||||
e_menu_item_callback_set(mi, _menu_settings_cb, inst);
|
||||
|
||||
int x, y;
|
||||
e_gadcon_canvas_zone_geometry_get(inst->gcc->gadcon, &x, &y, NULL, NULL);
|
||||
e_menu_activate_mouse(m, zone, x + ev->output.x, y + ev->output.y,
|
||||
1, 1, E_MENU_POP_DIRECTION_AUTO, ev->timestamp);
|
||||
}
|
||||
|
||||
static void
|
||||
_on_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
|
||||
{
|
||||
Evas_Event_Mouse_Down *ev = event_info;
|
||||
Instance *inst = data;
|
||||
if (ev->button == 1)
|
||||
e_iwd_popup_toggle(inst->gcc);
|
||||
else if (ev->button == 3)
|
||||
_show_menu(inst, ev);
|
||||
}
|
||||
|
||||
/* ----- helpers --------------------------------------------------------- */
|
||||
|
||||
static char *
|
||||
_theme_path(void)
|
||||
{
|
||||
static char buf[4096];
|
||||
if (!e_iwd || !e_iwd->module) return NULL;
|
||||
snprintf(buf, sizeof(buf), "%s/e-module-iwd.edj",
|
||||
e_module_dir_get(e_iwd->module));
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* ----- gadcon class ---------------------------------------------------- */
|
||||
|
||||
/* Gadcon init */
|
||||
static E_Gadcon_Client *
|
||||
_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style)
|
||||
{
|
||||
Instance *inst = E_NEW(Instance, 1);
|
||||
const char *path = _theme_path();
|
||||
Instance *inst;
|
||||
E_Gadcon_Client *gcc;
|
||||
Evas_Object *o;
|
||||
|
||||
/* themed edje is the gadcon o_base — its intrinsic min comes from the
|
||||
* theme group, just like the backlight module. */
|
||||
Evas_Object *base = edje_object_add(gc->evas);
|
||||
edje_object_file_set(base, path, "e/modules/iwd/main");
|
||||
evas_object_show(base);
|
||||
inst->o_base = base;
|
||||
DBG("Creating gadget instance");
|
||||
|
||||
/* the actual fdo icon goes into the swallow part */
|
||||
Evas_Object *icon = e_icon_add(gc->evas);
|
||||
e_icon_fill_inside_set(icon, EINA_TRUE);
|
||||
e_icon_fdo_icon_set(icon, "network-wireless");
|
||||
e_icon_preload_set(icon, EINA_TRUE);
|
||||
evas_object_show(icon);
|
||||
edje_object_part_swallow(base, "e.swallow.content", icon);
|
||||
inst->o_icon = icon;
|
||||
inst = E_NEW(Instance, 1);
|
||||
if (!inst) return NULL;
|
||||
|
||||
inst->gcc = e_gadcon_client_new(gc, name, id, style, base);
|
||||
inst->gcc->data = inst;
|
||||
/* Create edje object */
|
||||
o = edje_object_add(gc->evas);
|
||||
|
||||
evas_object_event_callback_add(base, EVAS_CALLBACK_MOUSE_DOWN,
|
||||
_on_mouse_down, inst);
|
||||
/* Load theme */
|
||||
char theme_path[PATH_MAX];
|
||||
snprintf(theme_path, sizeof(theme_path), "%s/e-module-iwd.edj",
|
||||
e_module_dir_get(iwd_mod->module));
|
||||
|
||||
_instances = eina_list_append(_instances, inst);
|
||||
_inst_refresh(inst);
|
||||
return inst->gcc;
|
||||
if (!edje_object_file_set(o, theme_path, "e/modules/iwd/main"))
|
||||
{
|
||||
/* Theme not found, use simple colored rectangle as fallback */
|
||||
WRN("Failed to load theme from %s", theme_path);
|
||||
evas_object_color_set(o, 100, 150, 200, 255);
|
||||
}
|
||||
|
||||
evas_object_show(o);
|
||||
|
||||
/* Pass the object directly to e_gadcon_client_new */
|
||||
gcc = e_gadcon_client_new(gc, name, id, style, o);
|
||||
if (!gcc)
|
||||
{
|
||||
evas_object_del(o);
|
||||
E_FREE(inst);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gcc->data = inst;
|
||||
inst->gcc = gcc;
|
||||
inst->icon = o;
|
||||
inst->gadget = o;
|
||||
|
||||
/* Add mouse event handler */
|
||||
evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN,
|
||||
_gadget_mouse_down_cb, inst);
|
||||
|
||||
/* Get first available device */
|
||||
Eina_List *devices = iwd_devices_get();
|
||||
if (devices && eina_list_count(devices) > 0)
|
||||
{
|
||||
inst->device = eina_list_data_get(devices);
|
||||
DBG("Using device: %s", inst->device->name ? inst->device->name : inst->device->path);
|
||||
}
|
||||
else
|
||||
{
|
||||
DBG("No Wi-Fi devices available");
|
||||
}
|
||||
|
||||
/* Start update timer */
|
||||
inst->update_timer = ecore_timer_add(2.0, _gadget_update_timer_cb, inst);
|
||||
_gadget_update(inst);
|
||||
|
||||
/* Add to module instances */
|
||||
if (iwd_mod)
|
||||
iwd_mod->instances = eina_list_append(iwd_mod->instances, inst);
|
||||
|
||||
return gcc;
|
||||
}
|
||||
|
||||
/* Gadcon shutdown */
|
||||
static void
|
||||
_gc_shutdown(E_Gadcon_Client *gcc)
|
||||
{
|
||||
Instance *inst = gcc->data;
|
||||
if (!inst) return;
|
||||
_instances = eina_list_remove(_instances, inst);
|
||||
if (inst->o_icon) evas_object_del(inst->o_icon);
|
||||
if (inst->o_base) evas_object_del(inst->o_base);
|
||||
Instance *inst;
|
||||
|
||||
DBG("Destroying gadget instance");
|
||||
|
||||
if (!(inst = gcc->data)) return;
|
||||
|
||||
/* Remove from module instances */
|
||||
if (iwd_mod)
|
||||
iwd_mod->instances = eina_list_remove(iwd_mod->instances, inst);
|
||||
|
||||
/* Delete popup if open */
|
||||
if (inst->popup)
|
||||
{
|
||||
iwd_popup_del(inst);
|
||||
}
|
||||
|
||||
/* Stop timer */
|
||||
if (inst->update_timer)
|
||||
{
|
||||
ecore_timer_del(inst->update_timer);
|
||||
inst->update_timer = NULL;
|
||||
}
|
||||
|
||||
/* Delete icon */
|
||||
if (inst->icon)
|
||||
evas_object_del(inst->icon);
|
||||
|
||||
E_FREE(inst);
|
||||
}
|
||||
|
||||
/* Gadcon orient */
|
||||
static void
|
||||
_gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient EINA_UNUSED)
|
||||
{
|
||||
Instance *inst = gcc->data;
|
||||
Evas_Coord mw = 0, mh = 0;
|
||||
if (!inst || !inst->o_base) return;
|
||||
edje_object_size_min_get(inst->o_base, &mw, &mh);
|
||||
Instance *inst;
|
||||
Evas_Coord mw, mh;
|
||||
|
||||
inst = gcc->data;
|
||||
if (!inst || !inst->icon) return;
|
||||
|
||||
mw = 0;
|
||||
mh = 0;
|
||||
edje_object_size_min_get(inst->icon, &mw, &mh);
|
||||
if ((mw < 1) || (mh < 1))
|
||||
edje_object_size_min_calc(inst->o_base, &mw, &mh);
|
||||
edje_object_size_min_calc(inst->icon, &mw, &mh);
|
||||
if (mw < 4) mw = 4;
|
||||
if (mh < 4) mh = 4;
|
||||
e_gadcon_client_aspect_set(gcc, mw, mh);
|
||||
e_gadcon_client_min_size_set(gcc, mw, mh);
|
||||
}
|
||||
|
||||
/* Gadcon label */
|
||||
static const char *
|
||||
_gc_label(const E_Gadcon_Client_Class *cc EINA_UNUSED) { return "iwd"; }
|
||||
|
||||
static Evas_Object *
|
||||
_gc_icon(const E_Gadcon_Client_Class *cc EINA_UNUSED, Evas *evas)
|
||||
_gc_label(const E_Gadcon_Client_Class *client_class EINA_UNUSED)
|
||||
{
|
||||
const char *path = _theme_path();
|
||||
Evas_Object *o = edje_object_add(evas);
|
||||
if (path) edje_object_file_set(o, path, "icon");
|
||||
return "IWD Wi-Fi";
|
||||
}
|
||||
|
||||
/* Gadcon icon */
|
||||
static Evas_Object *
|
||||
_gc_icon(const E_Gadcon_Client_Class *client_class EINA_UNUSED, Evas *evas)
|
||||
{
|
||||
Evas_Object *o;
|
||||
char theme_path[PATH_MAX];
|
||||
|
||||
o = edje_object_add(evas);
|
||||
|
||||
/* Try to load theme */
|
||||
if (iwd_mod && iwd_mod->module)
|
||||
{
|
||||
snprintf(theme_path, sizeof(theme_path), "%s/e-module-iwd.edj",
|
||||
e_module_dir_get(iwd_mod->module));
|
||||
|
||||
if (!edje_object_file_set(o, theme_path, "e/modules/iwd/main"))
|
||||
{
|
||||
/* Fallback to simple colored box */
|
||||
evas_object_color_set(o, 100, 150, 200, 255);
|
||||
evas_object_resize(o, 16, 16);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Fallback if module not initialized yet */
|
||||
evas_object_color_set(o, 100, 150, 200, 255);
|
||||
evas_object_resize(o, 16, 16);
|
||||
}
|
||||
|
||||
evas_object_show(o);
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Generate new ID */
|
||||
static const char *
|
||||
_gc_id_new(const E_Gadcon_Client_Class *cc)
|
||||
_gc_id_new(const E_Gadcon_Client_Class *client_class)
|
||||
{
|
||||
static char buf[128];
|
||||
snprintf(buf, sizeof(buf), "%s.%d", cc->name,
|
||||
eina_list_count(_instances) + 1);
|
||||
Mod *mod = iwd_mod;
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s.%d", client_class->name,
|
||||
mod ? eina_list_count(mod->instances) + 1 : 1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const E_Gadcon_Client_Class _gadcon_class =
|
||||
/* Mouse down callback */
|
||||
static void
|
||||
_gadget_mouse_down_cb(void *data, Evas *e EINA_UNUSED,
|
||||
Evas_Object *obj EINA_UNUSED,
|
||||
void *event_info)
|
||||
{
|
||||
GADCON_CLIENT_CLASS_VERSION,
|
||||
"iwd",
|
||||
{ _gc_init, _gc_shutdown, _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL, NULL },
|
||||
E_GADCON_CLIENT_STYLE_PLAIN
|
||||
};
|
||||
Instance *inst = data;
|
||||
Evas_Event_Mouse_Down *ev = event_info;
|
||||
|
||||
/* ----- public ---------------------------------------------------------- */
|
||||
if (!inst)
|
||||
{
|
||||
e_util_dialog_show("Debug", "Instance is NULL!");
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
e_iwd_gadget_init(void)
|
||||
{
|
||||
e_gadcon_provider_register(&_gadcon_class);
|
||||
if (e_iwd && e_iwd->manager)
|
||||
iwd_manager_listener_add(e_iwd->manager, _on_manager_change, NULL);
|
||||
if (ev->button == 1) /* Left click */
|
||||
{
|
||||
INF("Gadget clicked - popup=%p device=%p", inst->popup, inst->device);
|
||||
|
||||
if (inst->popup)
|
||||
{
|
||||
/* Close popup */
|
||||
INF("Closing popup");
|
||||
iwd_popup_del(inst);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Open popup */
|
||||
INF("Opening popup");
|
||||
iwd_popup_new(inst);
|
||||
|
||||
/* Debug: Check if popup was created */
|
||||
if (!inst->popup)
|
||||
{
|
||||
ERR("Failed to create popup!");
|
||||
e_util_dialog_show("IWD Debug",
|
||||
"Popup creation failed.<br>"
|
||||
"Check if iwd is running and wireless device exists.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
e_iwd_gadget_shutdown(void)
|
||||
/* Update gadget icon and tooltip */
|
||||
static void
|
||||
_gadget_update(Instance *inst)
|
||||
{
|
||||
if (e_iwd && e_iwd->manager)
|
||||
iwd_manager_listener_del(e_iwd->manager, _on_manager_change, NULL);
|
||||
e_gadcon_provider_unregister(&_gadcon_class);
|
||||
char buf[256];
|
||||
|
||||
if (!inst) return;
|
||||
|
||||
/* Update tooltip */
|
||||
if (inst->device)
|
||||
{
|
||||
if (inst->device->state && strcmp(inst->device->state, "connected") == 0)
|
||||
{
|
||||
snprintf(buf, sizeof(buf), "IWD Wi-Fi\nConnected: %s\nSignal: %s",
|
||||
inst->device->name ? inst->device->name : "Unknown",
|
||||
inst->device->connected_network ? "Good" : "");
|
||||
}
|
||||
else if (inst->device->state && strcmp(inst->device->state, "connecting") == 0)
|
||||
{
|
||||
snprintf(buf, sizeof(buf), "IWD Wi-Fi\nConnecting...");
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(buf, sizeof(buf), "IWD Wi-Fi\nDisconnected");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(buf, sizeof(buf), "IWD Wi-Fi\nNo device");
|
||||
}
|
||||
|
||||
/* Update icon appearance using Edje signals */
|
||||
extern IWD_State iwd_state_get(void);
|
||||
IWD_State state = iwd_state_get();
|
||||
const char *file = NULL;
|
||||
|
||||
/* Check if theme is loaded */
|
||||
if (inst->icon)
|
||||
{
|
||||
edje_object_file_get(inst->icon, &file, NULL);
|
||||
}
|
||||
|
||||
if (inst->icon && file)
|
||||
{
|
||||
/* Icon has theme loaded, use signals */
|
||||
switch (state)
|
||||
{
|
||||
case IWD_STATE_CONNECTED:
|
||||
edje_object_signal_emit(inst->icon, "e,state,connected", "e");
|
||||
edje_object_signal_emit(inst->icon, "e,signal,show", "e");
|
||||
break;
|
||||
case IWD_STATE_CONNECTING:
|
||||
edje_object_signal_emit(inst->icon, "e,state,connecting", "e");
|
||||
edje_object_signal_emit(inst->icon, "e,signal,hide", "e");
|
||||
break;
|
||||
case IWD_STATE_ERROR:
|
||||
edje_object_signal_emit(inst->icon, "e,state,error", "e");
|
||||
edje_object_signal_emit(inst->icon, "e,signal,hide", "e");
|
||||
break;
|
||||
default:
|
||||
edje_object_signal_emit(inst->icon, "e,state,disconnected", "e");
|
||||
edje_object_signal_emit(inst->icon, "e,signal,hide", "e");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Fallback to color changes if no theme */
|
||||
if (inst->device && inst->device->state)
|
||||
{
|
||||
if (strcmp(inst->device->state, "connected") == 0)
|
||||
evas_object_color_set(inst->icon, 100, 200, 100, 255); /* Green */
|
||||
else if (strcmp(inst->device->state, "connecting") == 0)
|
||||
evas_object_color_set(inst->icon, 200, 200, 100, 255); /* Yellow */
|
||||
else
|
||||
evas_object_color_set(inst->icon, 150, 150, 150, 255); /* Gray */
|
||||
}
|
||||
else
|
||||
{
|
||||
evas_object_color_set(inst->icon, 200, 100, 100, 255); /* Red - no device */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
e_iwd_gadget_update(void)
|
||||
/* Update timer callback */
|
||||
static Eina_Bool
|
||||
_gadget_update_timer_cb(void *data)
|
||||
{
|
||||
_on_manager_change(NULL, NULL);
|
||||
Instance *inst = data;
|
||||
|
||||
_gadget_update(inst);
|
||||
|
||||
return ECORE_CALLBACK_RENEW;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 "iwd/iwd_manager.h"
|
||||
#include "e_mod_gadget.h"
|
||||
#include "e_mod_popup.h"
|
||||
#include "e_mod_config.h"
|
||||
|
||||
E_Iwd_Module *e_iwd = NULL;
|
||||
/* Module metadata */
|
||||
E_API E_Module_Api e_modapi = {
|
||||
E_MODULE_API_VERSION,
|
||||
"IWD"
|
||||
};
|
||||
|
||||
EAPI E_Module_Api e_modapi = { E_MODULE_API_VERSION, "iwd" };
|
||||
/* Global module instance */
|
||||
Mod *iwd_mod = NULL;
|
||||
|
||||
EAPI void *
|
||||
/* Logging domain */
|
||||
int _e_iwd_log_dom = -1;
|
||||
|
||||
/* Forward declarations */
|
||||
static void _iwd_config_load(void);
|
||||
static void _iwd_config_free(void);
|
||||
|
||||
/* Module initialization */
|
||||
E_API void *
|
||||
e_modapi_init(E_Module *m)
|
||||
{
|
||||
e_iwd = E_NEW(E_Iwd_Module, 1);
|
||||
e_iwd->module = m;
|
||||
Mod *mod;
|
||||
|
||||
if (!eldbus_init())
|
||||
{
|
||||
E_FREE(e_iwd);
|
||||
return NULL;
|
||||
}
|
||||
/* Initialize logging */
|
||||
_e_iwd_log_dom = eina_log_domain_register("e-iwd", EINA_COLOR_CYAN);
|
||||
if (_e_iwd_log_dom < 0)
|
||||
{
|
||||
EINA_LOG_ERR("Could not register log domain 'e-iwd'");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
e_iwd->conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM);
|
||||
if (!e_iwd->conn)
|
||||
{
|
||||
eldbus_shutdown();
|
||||
E_FREE(e_iwd);
|
||||
return NULL;
|
||||
}
|
||||
INF("IWD Module initializing");
|
||||
|
||||
e_iwd_config_load();
|
||||
e_iwd->manager = iwd_manager_new(e_iwd->conn);
|
||||
e_iwd_popup_install_passphrase_handler();
|
||||
/* Allocate module structure */
|
||||
mod = E_NEW(Mod, 1);
|
||||
if (!mod)
|
||||
{
|
||||
ERR("Failed to allocate module structure");
|
||||
eina_log_domain_unregister(_e_iwd_log_dom);
|
||||
_e_iwd_log_dom = -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mod->module = m;
|
||||
mod->log_dom = _e_iwd_log_dom;
|
||||
iwd_mod = mod;
|
||||
|
||||
/* Initialize configuration */
|
||||
e_iwd_config_init();
|
||||
_iwd_config_load();
|
||||
|
||||
/* Initialize D-Bus and iwd subsystems (Phase 2 & 5) */
|
||||
iwd_state_init();
|
||||
iwd_device_init();
|
||||
iwd_network_init();
|
||||
|
||||
if (!iwd_dbus_init())
|
||||
{
|
||||
WRN("Failed to initialize D-Bus connection to iwd");
|
||||
iwd_state_set(IWD_STATE_ERROR);
|
||||
/* Continue anyway - we'll show error state in UI */
|
||||
}
|
||||
|
||||
if (!iwd_agent_init())
|
||||
{
|
||||
WRN("Failed to initialize iwd agent");
|
||||
}
|
||||
|
||||
/* Initialize gadget (Phase 3) */
|
||||
e_iwd_gadget_init();
|
||||
|
||||
return m;
|
||||
INF("IWD Module initialized successfully");
|
||||
return mod;
|
||||
}
|
||||
|
||||
EAPI int
|
||||
/* Module shutdown */
|
||||
E_API int
|
||||
e_modapi_shutdown(E_Module *m EINA_UNUSED)
|
||||
{
|
||||
if (!e_iwd) return 1;
|
||||
Mod *mod = iwd_mod;
|
||||
|
||||
if (!mod) return 0;
|
||||
|
||||
INF("IWD Module shutting down");
|
||||
|
||||
/* Shutdown gadget */
|
||||
e_iwd_gadget_shutdown();
|
||||
if (e_iwd->manager) iwd_manager_free(e_iwd->manager);
|
||||
e_iwd_config_save();
|
||||
if (e_iwd->conn) eldbus_connection_unref(e_iwd->conn);
|
||||
eldbus_shutdown();
|
||||
E_FREE(e_iwd);
|
||||
|
||||
/* Shutdown iwd subsystems (must happen before D-Bus shutdown) */
|
||||
iwd_network_shutdown();
|
||||
iwd_device_shutdown();
|
||||
iwd_state_shutdown();
|
||||
|
||||
/* Shutdown D-Bus (this frees all proxies and handlers) */
|
||||
iwd_agent_shutdown();
|
||||
iwd_dbus_shutdown();
|
||||
|
||||
/* Free configuration */
|
||||
_iwd_config_free();
|
||||
e_iwd_config_shutdown();
|
||||
|
||||
/* Free module structure */
|
||||
E_FREE(mod);
|
||||
iwd_mod = NULL;
|
||||
|
||||
/* Unregister logging */
|
||||
eina_log_domain_unregister(_e_iwd_log_dom);
|
||||
_e_iwd_log_dom = -1;
|
||||
|
||||
INF("IWD Module shutdown complete");
|
||||
return 1;
|
||||
}
|
||||
|
||||
EAPI int
|
||||
/* Module save */
|
||||
E_API int
|
||||
e_modapi_save(E_Module *m EINA_UNUSED)
|
||||
{
|
||||
e_iwd_config_save();
|
||||
return 1;
|
||||
Mod *mod = iwd_mod;
|
||||
|
||||
if (!mod || !mod->conf) return 0;
|
||||
|
||||
DBG("Saving module configuration");
|
||||
return e_config_domain_save("module.iwd", mod->conf_edd, mod->conf);
|
||||
}
|
||||
|
||||
/* Configuration management */
|
||||
void
|
||||
e_iwd_config_init(void)
|
||||
{
|
||||
Mod *mod = iwd_mod;
|
||||
|
||||
if (!mod) return;
|
||||
|
||||
/* Create configuration descriptor */
|
||||
mod->conf_edd = E_CONFIG_DD_NEW("IWD_Config", Config);
|
||||
if (!mod->conf_edd)
|
||||
{
|
||||
ERR("Failed to create config EDD");
|
||||
return;
|
||||
}
|
||||
|
||||
#undef T
|
||||
#undef D
|
||||
#define T Config
|
||||
#define D mod->conf_edd
|
||||
|
||||
E_CONFIG_VAL(D, T, config_version, INT);
|
||||
E_CONFIG_VAL(D, T, auto_connect, UCHAR);
|
||||
E_CONFIG_VAL(D, T, show_hidden_networks, UCHAR);
|
||||
E_CONFIG_VAL(D, T, signal_refresh_interval, INT);
|
||||
E_CONFIG_VAL(D, T, preferred_adapter, STR);
|
||||
|
||||
#undef T
|
||||
#undef D
|
||||
}
|
||||
|
||||
void
|
||||
e_iwd_config_shutdown(void)
|
||||
{
|
||||
Mod *mod = iwd_mod;
|
||||
|
||||
if (!mod) return;
|
||||
|
||||
if (mod->conf_edd)
|
||||
{
|
||||
E_CONFIG_DD_FREE(mod->conf_edd);
|
||||
mod->conf_edd = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_iwd_config_load(void)
|
||||
{
|
||||
Mod *mod = iwd_mod;
|
||||
|
||||
if (!mod || !mod->conf_edd) return;
|
||||
|
||||
/* Load configuration from disk */
|
||||
mod->conf = e_config_domain_load("module.iwd", mod->conf_edd);
|
||||
|
||||
if (mod->conf)
|
||||
{
|
||||
/* Check version */
|
||||
if ((mod->conf->config_version >> 16) < MOD_CONFIG_FILE_EPOCH)
|
||||
{
|
||||
/* Config too old, use defaults */
|
||||
WRN("Configuration version too old, using defaults");
|
||||
E_FREE(mod->conf);
|
||||
mod->conf = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create default configuration if needed */
|
||||
if (!mod->conf)
|
||||
{
|
||||
INF("Creating default configuration");
|
||||
mod->conf = E_NEW(Config, 1);
|
||||
if (mod->conf)
|
||||
{
|
||||
mod->conf->config_version = MOD_CONFIG_FILE_VERSION;
|
||||
mod->conf->auto_connect = EINA_TRUE;
|
||||
mod->conf->show_hidden_networks = EINA_FALSE;
|
||||
mod->conf->signal_refresh_interval = 5;
|
||||
mod->conf->preferred_adapter = NULL;
|
||||
|
||||
/* Save default config */
|
||||
e_config_domain_save("module.iwd", mod->conf_edd, mod->conf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_iwd_config_free(void)
|
||||
{
|
||||
Mod *mod = iwd_mod;
|
||||
|
||||
if (!mod || !mod->conf) return;
|
||||
|
||||
if (mod->conf->preferred_adapter)
|
||||
eina_stringshare_del(mod->conf->preferred_adapter);
|
||||
|
||||
E_FREE(mod->conf);
|
||||
mod->conf = NULL;
|
||||
}
|
||||
|
||||
/* Gadget implementations are in e_mod_gadget.c */
|
||||
|
|
|
|||
|
|
@ -2,26 +2,97 @@
|
|||
#define E_MOD_MAIN_H
|
||||
|
||||
#include <e.h>
|
||||
#include <Eina.h>
|
||||
#include <Eldbus.h>
|
||||
#include <Elementary.h>
|
||||
|
||||
typedef struct _E_Iwd_Module E_Iwd_Module;
|
||||
/* Module version information */
|
||||
#define MOD_CONFIG_FILE_EPOCH 0x0001
|
||||
#define MOD_CONFIG_FILE_GENERATION 0x0001
|
||||
#define MOD_CONFIG_FILE_VERSION \
|
||||
((MOD_CONFIG_FILE_EPOCH << 16) | MOD_CONFIG_FILE_GENERATION)
|
||||
|
||||
struct _E_Iwd_Module
|
||||
/* Forward declarations for iwd types */
|
||||
typedef struct _IWD_Device IWD_Device;
|
||||
|
||||
/* Configuration structure */
|
||||
typedef struct _Config
|
||||
{
|
||||
E_Module *module;
|
||||
Eldbus_Connection *conn;
|
||||
void *manager; /* Iwd_Manager * */
|
||||
void *gadget; /* gadget instance */
|
||||
void *config; /* E_Config_Dialog data */
|
||||
};
|
||||
int config_version;
|
||||
Eina_Bool auto_connect;
|
||||
Eina_Bool show_hidden_networks;
|
||||
int signal_refresh_interval;
|
||||
const char *preferred_adapter;
|
||||
} Config;
|
||||
|
||||
extern E_Iwd_Module *e_iwd;
|
||||
/* Module instance structure (gadget) */
|
||||
typedef struct _Instance
|
||||
{
|
||||
E_Gadcon_Client *gcc;
|
||||
Evas_Object *gadget;
|
||||
Evas_Object *icon;
|
||||
void *popup; /* E_Gadcon_Popup - void to avoid circular dependency */
|
||||
|
||||
EAPI extern E_Module_Api e_modapi;
|
||||
IWD_Device *device;
|
||||
Ecore_Timer *update_timer;
|
||||
} Instance;
|
||||
|
||||
EAPI void *e_modapi_init (E_Module *m);
|
||||
EAPI int e_modapi_shutdown (E_Module *m);
|
||||
EAPI int e_modapi_save (E_Module *m);
|
||||
/* Global module context */
|
||||
typedef struct _Mod
|
||||
{
|
||||
E_Module *module;
|
||||
E_Config_DD *conf_edd;
|
||||
Config *conf;
|
||||
Eina_List *instances;
|
||||
|
||||
/* D-Bus connection (will be initialized in Phase 2) */
|
||||
Eldbus_Connection *dbus_conn;
|
||||
|
||||
/* Logging domain */
|
||||
int log_dom;
|
||||
} Mod;
|
||||
|
||||
/* Global module instance */
|
||||
extern Mod *iwd_mod;
|
||||
|
||||
/* Logging macros */
|
||||
extern int _e_iwd_log_dom;
|
||||
#undef DBG
|
||||
#undef INF
|
||||
#undef WRN
|
||||
#undef ERR
|
||||
#define DBG(...) EINA_LOG_DOM_DBG(_e_iwd_log_dom, __VA_ARGS__)
|
||||
#define INF(...) EINA_LOG_DOM_INFO(_e_iwd_log_dom, __VA_ARGS__)
|
||||
#define WRN(...) EINA_LOG_DOM_WARN(_e_iwd_log_dom, __VA_ARGS__)
|
||||
#define ERR(...) EINA_LOG_DOM_ERR(_e_iwd_log_dom, __VA_ARGS__)
|
||||
|
||||
/* Module API functions */
|
||||
E_API extern E_Module_Api e_modapi;
|
||||
E_API void *e_modapi_init(E_Module *m);
|
||||
E_API int e_modapi_shutdown(E_Module *m);
|
||||
E_API int e_modapi_save(E_Module *m);
|
||||
|
||||
/* Configuration functions */
|
||||
void e_iwd_config_init(void);
|
||||
void e_iwd_config_shutdown(void);
|
||||
void e_iwd_config_show(void);
|
||||
|
||||
/* Gadget functions */
|
||||
void e_iwd_gadget_init(void);
|
||||
void e_iwd_gadget_shutdown(void);
|
||||
|
||||
/* Popup functions */
|
||||
void iwd_popup_new(Instance *inst);
|
||||
void iwd_popup_del(Instance *inst);
|
||||
|
||||
/* UI dialog functions */
|
||||
#include "ui/wifi_auth.h"
|
||||
#include "ui/wifi_hidden.h"
|
||||
|
||||
/* D-Bus functions */
|
||||
#include "iwd/iwd_dbus.h"
|
||||
#include "iwd/iwd_device.h"
|
||||
#include "iwd/iwd_network.h"
|
||||
#include "iwd/iwd_agent.h"
|
||||
#include "iwd/iwd_state.h"
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,445 +1,316 @@
|
|||
#include "e_mod_main.h"
|
||||
#include "e_mod_popup.h"
|
||||
#include "iwd/iwd_manager.h"
|
||||
#include "iwd/iwd_device.h"
|
||||
#include "iwd/iwd_network.h"
|
||||
#include "iwd/iwd_agent.h"
|
||||
#include "ui/wifi_auth.h"
|
||||
#include "ui/wifi_hidden.h"
|
||||
#include <e_gadcon_popup.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct _Popup
|
||||
/* Forward declarations */
|
||||
static void _popup_comp_del_cb(void *data, Evas_Object *obj);
|
||||
static void _button_rescan_cb(void *data, Evas_Object *obj, void *event_info);
|
||||
static void _button_disconnect_cb(void *data, Evas_Object *obj, void *event_info);
|
||||
static void _button_hidden_cb(void *data, Evas_Object *obj, void *event_info);
|
||||
static Eina_Bool _popup_reopen_cb(void *data);
|
||||
static void _network_selected_cb(void *data, Evas_Object *obj, void *event_info);
|
||||
|
||||
/* Create popup */
|
||||
void
|
||||
iwd_popup_new(Instance *inst)
|
||||
{
|
||||
E_Gadcon_Popup *gp;
|
||||
Evas_Object *box;
|
||||
Evas_Object *status_lbl;
|
||||
Evas_Object *list;
|
||||
Evas_Object *btn_scan;
|
||||
Evas_Object *btn_toggle;
|
||||
Evas_Object *btn_hidden;
|
||||
Evas_Object *btn_disconnect; /* shown only when connected */
|
||||
Evas_Object *action_row;
|
||||
Eina_Bool listening;
|
||||
} Popup;
|
||||
|
||||
static Popup *_popup = NULL;
|
||||
|
||||
/* Pending passphrase request from the agent — only one at a time. */
|
||||
static Iwd_Agent_Request *_pending_req = NULL;
|
||||
/* Tracked so iwd's Cancel(reason) can tear down the dialog. */
|
||||
static Evas_Object *_pending_dialog = NULL;
|
||||
/* One-shot passphrase pre-armed by the hidden-network dialog. */
|
||||
static char *_hidden_pending_pass = NULL;
|
||||
|
||||
/* ----- helpers --------------------------------------------------------- */
|
||||
|
||||
static const char *
|
||||
_state_label(Iwd_State s)
|
||||
{
|
||||
switch (s) {
|
||||
case IWD_STATE_OFF: return "Wi-Fi disabled";
|
||||
case IWD_STATE_IDLE: return "Disconnected";
|
||||
case IWD_STATE_SCANNING: return "Scanning…";
|
||||
case IWD_STATE_CONNECTING: return "Connecting…";
|
||||
case IWD_STATE_CONNECTED: return "Connected";
|
||||
case IWD_STATE_ERROR: return "Error";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static const char *
|
||||
_sec_label(Iwd_Security s)
|
||||
{
|
||||
switch (s) {
|
||||
case IWD_SEC_OPEN: return "open";
|
||||
case IWD_SEC_PSK: return "WPA";
|
||||
case IWD_SEC_8021X: return "802.1X";
|
||||
case IWD_SEC_WEP: return "WEP";
|
||||
case IWD_SEC_UNKNOWN: return "?";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static int
|
||||
_net_cmp(const void *a, const void *b)
|
||||
{
|
||||
const Iwd_Network *na = a, *nb = b;
|
||||
if (na->connected != nb->connected) return nb->connected - na->connected;
|
||||
if (na->known_path && !nb->known_path) return -1;
|
||||
if (!na->known_path && nb->known_path) return 1;
|
||||
/* Higher signal first within same class. */
|
||||
int ta = iwd_network_signal_tier(na);
|
||||
int tb = iwd_network_signal_tier(nb);
|
||||
if (ta != tb) return tb - ta;
|
||||
if (!na->ssid) return 1;
|
||||
if (!nb->ssid) return -1;
|
||||
return strcasecmp(na->ssid, nb->ssid);
|
||||
}
|
||||
|
||||
static const char *
|
||||
_signal_bars(int tier)
|
||||
{
|
||||
switch (tier)
|
||||
{
|
||||
case 4: return "▂▄▆█";
|
||||
case 3: return "▂▄▆ ";
|
||||
case 2: return "▂▄ ";
|
||||
case 1: return "▂ ";
|
||||
default: return " ";
|
||||
}
|
||||
}
|
||||
|
||||
/* First device that has a station; used for "Disconnect" and hidden connect. */
|
||||
static Iwd_Device *
|
||||
_active_device(void)
|
||||
{
|
||||
if (!e_iwd || !e_iwd->manager) return NULL;
|
||||
const Eina_Hash *h = iwd_manager_devices(e_iwd->manager);
|
||||
if (!h) return NULL;
|
||||
Eina_Iterator *it = eina_hash_iterator_data_new((Eina_Hash *)h);
|
||||
Iwd_Device *d, *best = NULL;
|
||||
EINA_ITERATOR_FOREACH(it, d)
|
||||
{
|
||||
if (!d->has_station) continue;
|
||||
if (!best) best = d;
|
||||
if (d->connected_network) { best = d; break; }
|
||||
}
|
||||
eina_iterator_free(it);
|
||||
return best;
|
||||
}
|
||||
|
||||
/* ----- list rendering -------------------------------------------------- */
|
||||
|
||||
static void
|
||||
_on_net_clicked(void *data, Evas_Object *obj EINA_UNUSED, void *ev EINA_UNUSED)
|
||||
{
|
||||
Iwd_Network *n = data;
|
||||
if (!n) return;
|
||||
iwd_network_connect(n);
|
||||
}
|
||||
|
||||
static void
|
||||
_on_net_forget(void *data, Evas_Object *obj EINA_UNUSED, void *ev EINA_UNUSED)
|
||||
{
|
||||
Iwd_Network *n = data;
|
||||
if (!n) return;
|
||||
iwd_network_forget(n);
|
||||
}
|
||||
|
||||
static void
|
||||
_rebuild_list(Popup *p)
|
||||
{
|
||||
if (!p->list || !e_iwd || !e_iwd->manager) return;
|
||||
elm_box_clear(p->list);
|
||||
|
||||
/* When the radio is off, hide the (now-stale) network list entirely. */
|
||||
if (iwd_manager_state(e_iwd->manager) == IWD_STATE_OFF) return;
|
||||
|
||||
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
|
||||
if (!h) return;
|
||||
|
||||
/* Snapshot into a list so we can sort. */
|
||||
Eina_List *items = NULL;
|
||||
Eina_Iterator *it = eina_hash_iterator_data_new((Eina_Hash *)h);
|
||||
Iwd_Network *n;
|
||||
EINA_ITERATOR_FOREACH(it, n) items = eina_list_append(items, n);
|
||||
eina_iterator_free(it);
|
||||
items = eina_list_sort(items, eina_list_count(items), _net_cmp);
|
||||
|
||||
Evas_Object *list, *box, *button, *label;
|
||||
E_Gadcon_Popup *popup;
|
||||
Evas_Coord w, h;
|
||||
IWD_Network *net;
|
||||
Eina_List *l;
|
||||
EINA_LIST_FOREACH(items, l, n)
|
||||
{
|
||||
Evas_Object *row = elm_box_add(p->list);
|
||||
elm_box_horizontal_set(row, EINA_TRUE);
|
||||
elm_box_padding_set(row, 4, 0);
|
||||
evas_object_size_hint_weight_set(row, EVAS_HINT_EXPAND, 0);
|
||||
evas_object_size_hint_align_set(row, EVAS_HINT_FILL, 0);
|
||||
|
||||
Evas_Object *btn = elm_button_add(row);
|
||||
/* Truncate long SSIDs so the row never forces horizontal scrolling. */
|
||||
const char *raw_ssid = n->ssid ? n->ssid : "(hidden)";
|
||||
char ssid_buf[32];
|
||||
const int max_ssid = 22;
|
||||
if ((int)strlen(raw_ssid) > max_ssid)
|
||||
{
|
||||
snprintf(ssid_buf, sizeof(ssid_buf), "%.*s…", max_ssid - 1, raw_ssid);
|
||||
raw_ssid = ssid_buf;
|
||||
}
|
||||
char label[256];
|
||||
snprintf(label, sizeof(label), "%s %s%s [%s]%s",
|
||||
_signal_bars(iwd_network_signal_tier(n)),
|
||||
n->known_path ? "★ " : " ",
|
||||
raw_ssid,
|
||||
_sec_label(n->security),
|
||||
n->connected ? " ✔" : "");
|
||||
elm_object_text_set(btn, label);
|
||||
evas_object_size_hint_weight_set(btn, EVAS_HINT_EXPAND, 0);
|
||||
evas_object_size_hint_align_set(btn, EVAS_HINT_FILL, 0);
|
||||
evas_object_smart_callback_add(btn, "clicked", _on_net_clicked, n);
|
||||
elm_box_pack_end(row, btn);
|
||||
evas_object_show(btn);
|
||||
if (!inst)
|
||||
{
|
||||
ERR("iwd_popup_new: inst is NULL");
|
||||
return;
|
||||
}
|
||||
|
||||
if (n->known_path)
|
||||
{
|
||||
Evas_Object *fb = elm_button_add(row);
|
||||
elm_object_text_set(fb, "✕");
|
||||
elm_object_tooltip_text_set(fb, "Forget network");
|
||||
evas_object_smart_callback_add(fb, "clicked", _on_net_forget, n);
|
||||
elm_box_pack_end(row, fb);
|
||||
evas_object_show(fb);
|
||||
}
|
||||
if (inst->popup)
|
||||
{
|
||||
DBG("Popup already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
elm_box_pack_end(p->list, row);
|
||||
evas_object_show(row);
|
||||
}
|
||||
eina_list_free(items);
|
||||
}
|
||||
INF("Creating popup for instance %p", inst);
|
||||
|
||||
static void
|
||||
_refresh(Popup *p)
|
||||
{
|
||||
if (!p || !e_iwd || !e_iwd->manager) return;
|
||||
Iwd_State s = iwd_manager_state(e_iwd->manager);
|
||||
if (p->status_lbl)
|
||||
elm_object_text_set(p->status_lbl, _state_label(s));
|
||||
if (p->btn_toggle)
|
||||
elm_object_text_set(p->btn_toggle, s == IWD_STATE_OFF ? "Enable" : "Disable");
|
||||
if (p->btn_scan)
|
||||
elm_object_disabled_set(p->btn_scan, s == IWD_STATE_OFF);
|
||||
if (p->btn_hidden)
|
||||
elm_object_disabled_set(p->btn_hidden, s == IWD_STATE_OFF);
|
||||
if (p->btn_disconnect)
|
||||
{
|
||||
Eina_Bool show = (s == IWD_STATE_CONNECTED);
|
||||
if (show) evas_object_show(p->btn_disconnect);
|
||||
else evas_object_hide(p->btn_disconnect);
|
||||
}
|
||||
_rebuild_list(p);
|
||||
}
|
||||
/* Create popup */
|
||||
popup = e_gadcon_popup_new(inst->gcc, 0);
|
||||
if (!popup)
|
||||
{
|
||||
ERR("e_gadcon_popup_new failed!");
|
||||
e_util_dialog_show("IWD Error", "Failed to create gadcon popup");
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
_on_manager_change(void *data, Iwd_Manager *m EINA_UNUSED)
|
||||
{
|
||||
_refresh(data);
|
||||
}
|
||||
|
||||
/* ----- action buttons -------------------------------------------------- */
|
||||
|
||||
static void _on_rescan (void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED)
|
||||
{
|
||||
if (e_iwd && e_iwd->manager) iwd_manager_scan_request(e_iwd->manager);
|
||||
}
|
||||
static void _on_toggle(void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED)
|
||||
{
|
||||
if (!e_iwd || !e_iwd->manager) return;
|
||||
Eina_Bool off = (iwd_manager_state(e_iwd->manager) == IWD_STATE_OFF);
|
||||
iwd_manager_set_powered(e_iwd->manager, off);
|
||||
}
|
||||
static void _on_disconnect(void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED)
|
||||
{
|
||||
Iwd_Device *dev = _active_device();
|
||||
if (dev) iwd_device_disconnect(dev);
|
||||
}
|
||||
|
||||
static void
|
||||
_on_hidden_done(void *data EINA_UNUSED, const char *ssid, const char *pass, Eina_Bool ok)
|
||||
{
|
||||
if (!ok || !ssid || !*ssid) return;
|
||||
Iwd_Device *dev = _active_device();
|
||||
if (!dev) return;
|
||||
/* Pre-arm the agent reply so the next RequestPassphrase from iwd is
|
||||
* answered automatically. If the network turns out to be open, the
|
||||
* stashed passphrase is simply never consumed. */
|
||||
if (pass && *pass) _hidden_pending_pass = strdup(pass);
|
||||
iwd_device_connect_hidden(dev, ssid);
|
||||
}
|
||||
|
||||
static void _on_hidden(void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED)
|
||||
{
|
||||
wifi_hidden_prompt(_popup ? _popup->box : e_comp->elm, _on_hidden_done, NULL);
|
||||
}
|
||||
|
||||
/* ----- passphrase plumbing -------------------------------------------- */
|
||||
|
||||
static void
|
||||
_on_auth_done(void *data EINA_UNUSED, const char *pass, Eina_Bool ok)
|
||||
{
|
||||
_pending_dialog = NULL;
|
||||
if (!_pending_req) return;
|
||||
if (ok) iwd_agent_reply (_pending_req, pass ? pass : "");
|
||||
else iwd_agent_cancel(_pending_req);
|
||||
_pending_req = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
_on_agent_cancel(void *data EINA_UNUSED, const char *reason EINA_UNUSED)
|
||||
{
|
||||
/* iwd dropped the auth attempt — close any open dialog. The dialog's
|
||||
* DEL handler will fire _on_auth_done(ok=FALSE), but _pending_req has
|
||||
* already been consumed by iwd, so clear it first to avoid double-cancel. */
|
||||
_pending_req = NULL;
|
||||
if (_pending_dialog)
|
||||
{
|
||||
Evas_Object *d = _pending_dialog;
|
||||
_pending_dialog = NULL;
|
||||
evas_object_del(d);
|
||||
}
|
||||
}
|
||||
|
||||
static void _on_passphrase_request(void *data, Iwd_Agent_Request *req, const char *netpath);
|
||||
|
||||
void
|
||||
e_iwd_popup_install_passphrase_handler(void)
|
||||
{
|
||||
if (e_iwd && e_iwd->manager)
|
||||
{
|
||||
iwd_manager_set_passphrase_handler(e_iwd->manager, _on_passphrase_request, NULL);
|
||||
iwd_manager_set_cancel_handler (e_iwd->manager, _on_agent_cancel, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_on_passphrase_request(void *data EINA_UNUSED, Iwd_Agent_Request *req, const char *netpath)
|
||||
{
|
||||
/* If the user just kicked off a hidden-network connect with a passphrase,
|
||||
* answer this request automatically without prompting. */
|
||||
if (_hidden_pending_pass)
|
||||
{
|
||||
iwd_agent_reply(req, _hidden_pending_pass);
|
||||
free(_hidden_pending_pass);
|
||||
_hidden_pending_pass = NULL;
|
||||
return;
|
||||
}
|
||||
if (_pending_req)
|
||||
{
|
||||
iwd_agent_cancel(req);
|
||||
return;
|
||||
}
|
||||
_pending_req = req;
|
||||
|
||||
/* Look up the network for a friendly SSID, if we have it. */
|
||||
const char *ssid = "network";
|
||||
if (e_iwd && e_iwd->manager)
|
||||
{
|
||||
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
|
||||
if (h)
|
||||
{
|
||||
Iwd_Network *n = eina_hash_find(h, netpath);
|
||||
if (n && n->ssid) ssid = n->ssid;
|
||||
}
|
||||
}
|
||||
const char *sec = NULL;
|
||||
if (e_iwd && e_iwd->manager)
|
||||
{
|
||||
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
|
||||
if (h)
|
||||
{
|
||||
Iwd_Network *n = eina_hash_find(h, netpath);
|
||||
if (n) sec = _sec_label(n->security);
|
||||
}
|
||||
}
|
||||
_pending_dialog = wifi_auth_prompt(_popup ? _popup->box : e_comp->elm,
|
||||
ssid, sec, _on_auth_done, NULL);
|
||||
}
|
||||
|
||||
/* ----- popup lifecycle ------------------------------------------------- */
|
||||
|
||||
static void
|
||||
_destroy(void)
|
||||
{
|
||||
if (!_popup) return;
|
||||
if (_popup->listening && e_iwd && e_iwd->manager)
|
||||
iwd_manager_listener_del(e_iwd->manager, _on_manager_change, _popup);
|
||||
if (_popup->gp) e_object_del(E_OBJECT(_popup->gp));
|
||||
free(_popup);
|
||||
_popup = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
e_iwd_popup_close(void) { _destroy(); }
|
||||
|
||||
void
|
||||
e_iwd_popup_refresh(void) { if (_popup) _refresh(_popup); }
|
||||
|
||||
void
|
||||
e_iwd_popup_toggle(E_Gadcon_Client *gcc)
|
||||
{
|
||||
if (_popup) { _destroy(); return; }
|
||||
if (!gcc || !e_iwd) return;
|
||||
|
||||
Popup *p = calloc(1, sizeof(*p));
|
||||
_popup = p;
|
||||
|
||||
p->gp = e_gadcon_popup_new(gcc, EINA_FALSE);
|
||||
|
||||
Evas *evas = evas_object_evas_get(gcc->o_base);
|
||||
Evas_Object *box = elm_box_add(e_comp->elm);
|
||||
(void)evas;
|
||||
elm_box_padding_set(box, 0, 4);
|
||||
evas_object_size_hint_min_set(box, 240, 320);
|
||||
p->box = box;
|
||||
|
||||
/* Status line */
|
||||
Evas_Object *st = elm_label_add(box);
|
||||
p->status_lbl = st;
|
||||
elm_box_pack_end(box, st);
|
||||
evas_object_show(st);
|
||||
|
||||
/* Network list (vertical box of buttons — keeps deps minimal) */
|
||||
Evas_Object *scroller = elm_scroller_add(box);
|
||||
evas_object_size_hint_weight_set(scroller, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
|
||||
evas_object_size_hint_align_set(scroller, EVAS_HINT_FILL, EVAS_HINT_FILL);
|
||||
Evas_Object *list_box = elm_box_add(scroller);
|
||||
evas_object_size_hint_weight_set(list_box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
|
||||
evas_object_size_hint_align_set(list_box, EVAS_HINT_FILL, EVAS_HINT_FILL);
|
||||
elm_object_content_set(scroller, list_box);
|
||||
evas_object_show(list_box);
|
||||
elm_box_pack_end(box, scroller);
|
||||
evas_object_show(scroller);
|
||||
p->list = list_box;
|
||||
|
||||
/* Action row */
|
||||
Evas_Object *row = elm_box_add(box);
|
||||
elm_box_horizontal_set(row, EINA_TRUE);
|
||||
elm_box_padding_set(row, 4, 0);
|
||||
p->action_row = row;
|
||||
|
||||
p->btn_scan = elm_button_add(row);
|
||||
elm_object_text_set(p->btn_scan, "Rescan");
|
||||
evas_object_smart_callback_add(p->btn_scan, "clicked", _on_rescan, NULL);
|
||||
elm_box_pack_end(row, p->btn_scan); evas_object_show(p->btn_scan);
|
||||
|
||||
p->btn_toggle = elm_button_add(row);
|
||||
elm_object_text_set(p->btn_toggle, "Disable");
|
||||
evas_object_smart_callback_add(p->btn_toggle, "clicked", _on_toggle, NULL);
|
||||
elm_box_pack_end(row, p->btn_toggle); evas_object_show(p->btn_toggle);
|
||||
|
||||
p->btn_hidden = elm_button_add(row);
|
||||
elm_object_text_set(p->btn_hidden, "Hidden…");
|
||||
evas_object_smart_callback_add(p->btn_hidden, "clicked", _on_hidden, NULL);
|
||||
elm_box_pack_end(row, p->btn_hidden); evas_object_show(p->btn_hidden);
|
||||
|
||||
p->btn_disconnect = elm_button_add(row);
|
||||
elm_object_text_set(p->btn_disconnect, "Disconnect");
|
||||
evas_object_smart_callback_add(p->btn_disconnect, "clicked", _on_disconnect, NULL);
|
||||
elm_box_pack_end(row, p->btn_disconnect);
|
||||
/* Visibility is driven by _refresh() based on connection state. */
|
||||
|
||||
elm_box_pack_end(box, row);
|
||||
evas_object_show(row);
|
||||
inst->popup = (void *)popup;
|
||||
INF("Popup created: %p", popup);
|
||||
|
||||
/* Create main box */
|
||||
box = elm_box_add(e_comp->elm);
|
||||
elm_box_horizontal_set(box, EINA_FALSE);
|
||||
elm_box_padding_set(box, 0, 5);
|
||||
evas_object_show(box);
|
||||
e_gadcon_popup_content_set(p->gp, box);
|
||||
e_gadcon_popup_show(p->gp);
|
||||
|
||||
if (e_iwd->manager)
|
||||
{
|
||||
iwd_manager_listener_add(e_iwd->manager, _on_manager_change, p);
|
||||
p->listening = EINA_TRUE;
|
||||
iwd_manager_set_passphrase_handler(e_iwd->manager, _on_passphrase_request, NULL);
|
||||
iwd_manager_scan_request(e_iwd->manager);
|
||||
}
|
||||
_refresh(p);
|
||||
/* Title */
|
||||
label = elm_label_add(box);
|
||||
elm_object_text_set(label, "<b>IWD Wi-Fi Manager</b>");
|
||||
elm_box_pack_end(box, label);
|
||||
evas_object_show(label);
|
||||
|
||||
/* Current connection status */
|
||||
if (inst->device)
|
||||
{
|
||||
Evas_Object *frame = elm_frame_add(box);
|
||||
elm_object_text_set(frame, "Current Connection");
|
||||
|
||||
Evas_Object *status_box = elm_box_add(frame);
|
||||
|
||||
char buf[256];
|
||||
if (inst->device->state && strcmp(inst->device->state, "connected") == 0)
|
||||
{
|
||||
snprintf(buf, sizeof(buf), "Connected to: %s",
|
||||
inst->device->name ? inst->device->name : "Unknown");
|
||||
}
|
||||
else if (inst->device->state && strcmp(inst->device->state, "connecting") == 0)
|
||||
{
|
||||
snprintf(buf, sizeof(buf), "Connecting...");
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(buf, sizeof(buf), "Disconnected");
|
||||
}
|
||||
|
||||
Evas_Object *status_label = elm_label_add(status_box);
|
||||
elm_object_text_set(status_label, buf);
|
||||
elm_box_pack_end(status_box, status_label);
|
||||
evas_object_show(status_label);
|
||||
|
||||
/* Disconnect button if connected */
|
||||
if (inst->device->state && strcmp(inst->device->state, "connected") == 0)
|
||||
{
|
||||
button = elm_button_add(status_box);
|
||||
elm_object_text_set(button, "Disconnect");
|
||||
evas_object_smart_callback_add(button, "clicked", _button_disconnect_cb, inst);
|
||||
elm_box_pack_end(status_box, button);
|
||||
evas_object_show(button);
|
||||
}
|
||||
|
||||
elm_object_content_set(frame, status_box);
|
||||
evas_object_show(status_box);
|
||||
elm_box_pack_end(box, frame);
|
||||
evas_object_show(frame);
|
||||
}
|
||||
|
||||
/* Available networks */
|
||||
Evas_Object *frame = elm_frame_add(box);
|
||||
elm_object_text_set(frame, "Available Networks");
|
||||
|
||||
list = elm_list_add(frame);
|
||||
elm_list_mode_set(list, ELM_LIST_COMPRESS);
|
||||
|
||||
/* Add networks to list */
|
||||
Eina_List *networks = iwd_networks_get();
|
||||
int count = 0;
|
||||
|
||||
EINA_LIST_FOREACH(networks, l, net)
|
||||
{
|
||||
if (net->name)
|
||||
{
|
||||
char item_text[256];
|
||||
const char *security = "Open";
|
||||
|
||||
if (net->type)
|
||||
{
|
||||
if (strcmp(net->type, "psk") == 0)
|
||||
security = "WPA2";
|
||||
else if (strcmp(net->type, "8021x") == 0)
|
||||
security = "Enterprise";
|
||||
}
|
||||
|
||||
snprintf(item_text, sizeof(item_text), "%s (%s)%s",
|
||||
net->name, security,
|
||||
net->known ? " *" : "");
|
||||
|
||||
elm_list_item_append(list, item_text, NULL, NULL, _network_selected_cb, net);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
elm_list_item_append(list, "No networks found", NULL, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
/* Set select mode to always */
|
||||
elm_list_select_mode_set(list, ELM_OBJECT_SELECT_MODE_ALWAYS);
|
||||
elm_list_go(list);
|
||||
elm_object_content_set(frame, list);
|
||||
evas_object_show(list);
|
||||
elm_box_pack_end(box, frame);
|
||||
evas_object_show(frame);
|
||||
|
||||
/* Action buttons */
|
||||
Evas_Object *button_box = elm_box_add(box);
|
||||
elm_box_horizontal_set(button_box, EINA_TRUE);
|
||||
elm_box_padding_set(button_box, 5, 0);
|
||||
|
||||
/* Rescan button */
|
||||
button = elm_button_add(button_box);
|
||||
elm_object_text_set(button, "Rescan");
|
||||
evas_object_smart_callback_add(button, "clicked", _button_rescan_cb, inst);
|
||||
elm_box_pack_end(button_box, button);
|
||||
evas_object_show(button);
|
||||
|
||||
/* Hidden network button */
|
||||
button = elm_button_add(button_box);
|
||||
elm_object_text_set(button, "Hidden...");
|
||||
evas_object_smart_callback_add(button, "clicked", _button_hidden_cb, inst);
|
||||
elm_box_pack_end(button_box, button);
|
||||
evas_object_show(button);
|
||||
|
||||
elm_box_pack_end(box, button_box);
|
||||
evas_object_show(button_box);
|
||||
|
||||
/* Set popup content */
|
||||
e_gadcon_popup_content_set(popup, box);
|
||||
|
||||
/* Size the popup */
|
||||
evas_object_geometry_get(box, NULL, NULL, &w, &h);
|
||||
if (w < 200) w = 200;
|
||||
if (h < 150) h = 150;
|
||||
if (w > 400) w = 400;
|
||||
if (h > 500) h = 500;
|
||||
|
||||
evas_object_resize(box, w, h);
|
||||
|
||||
/* Show popup */
|
||||
e_gadcon_popup_show(popup);
|
||||
|
||||
/* Auto-close on escape */
|
||||
e_comp_object_util_autoclose(popup->comp_object,
|
||||
_popup_comp_del_cb,
|
||||
e_comp_object_util_autoclose_on_escape,
|
||||
inst);
|
||||
|
||||
DBG("Popup created");
|
||||
}
|
||||
|
||||
/* Delete popup */
|
||||
void
|
||||
iwd_popup_del(Instance *inst)
|
||||
{
|
||||
E_Gadcon_Popup *popup;
|
||||
|
||||
if (!inst) return;
|
||||
if (!inst->popup) return;
|
||||
|
||||
DBG("Deleting popup");
|
||||
|
||||
popup = (E_Gadcon_Popup *)inst->popup;
|
||||
e_object_del(E_OBJECT(popup));
|
||||
inst->popup = NULL;
|
||||
}
|
||||
|
||||
/* Comp delete callback */
|
||||
static void
|
||||
_popup_comp_del_cb(void *data, Evas_Object *obj EINA_UNUSED)
|
||||
{
|
||||
Instance *inst = data;
|
||||
|
||||
if (inst && inst->popup)
|
||||
iwd_popup_del(inst);
|
||||
}
|
||||
|
||||
/* Rescan button callback */
|
||||
static void
|
||||
_button_rescan_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
|
||||
{
|
||||
Instance *inst = data;
|
||||
|
||||
if (!inst || !inst->device) return;
|
||||
|
||||
DBG("Rescan requested");
|
||||
iwd_device_scan(inst->device);
|
||||
|
||||
/* Close and reopen popup to refresh */
|
||||
iwd_popup_del(inst);
|
||||
ecore_timer_add(0.5, _popup_reopen_cb, inst);
|
||||
}
|
||||
|
||||
/* Timer callback to reopen popup */
|
||||
static Eina_Bool
|
||||
_popup_reopen_cb(void *data)
|
||||
{
|
||||
Instance *inst = data;
|
||||
if (inst)
|
||||
iwd_popup_new(inst);
|
||||
return ECORE_CALLBACK_CANCEL;
|
||||
}
|
||||
|
||||
/* Disconnect button callback */
|
||||
static void
|
||||
_button_disconnect_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
|
||||
{
|
||||
Instance *inst = data;
|
||||
|
||||
if (!inst || !inst->device) return;
|
||||
|
||||
DBG("Disconnect requested");
|
||||
iwd_device_disconnect(inst->device);
|
||||
|
||||
/* Close popup */
|
||||
iwd_popup_del(inst);
|
||||
}
|
||||
|
||||
/* Hidden network button callback */
|
||||
static void
|
||||
_button_hidden_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
|
||||
{
|
||||
Instance *inst = data;
|
||||
|
||||
if (!inst) return;
|
||||
|
||||
DBG("Hidden network button clicked");
|
||||
|
||||
extern void wifi_hidden_dialog_show(Instance *inst);
|
||||
wifi_hidden_dialog_show(inst);
|
||||
|
||||
/* Close popup */
|
||||
iwd_popup_del(inst);
|
||||
}
|
||||
|
||||
/* Network selected callback */
|
||||
static void
|
||||
_network_selected_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
|
||||
{
|
||||
IWD_Network *net = data;
|
||||
|
||||
if (!net || !net->name)
|
||||
{
|
||||
DBG("Invalid network selected");
|
||||
return;
|
||||
}
|
||||
|
||||
INF("Network selected: %s (type: %s)", net->name, net->type ? net->type : "unknown");
|
||||
|
||||
/* Check if network requires authentication */
|
||||
if (net->type && (strcmp(net->type, "psk") == 0 || strcmp(net->type, "8021x") == 0))
|
||||
{
|
||||
/* Secured network - need to show auth dialog first */
|
||||
/* Get instance from module */
|
||||
if (iwd_mod && iwd_mod->instances)
|
||||
{
|
||||
Instance *inst = eina_list_data_get(iwd_mod->instances);
|
||||
if (inst)
|
||||
{
|
||||
extern void wifi_auth_dialog_show(Instance *inst, IWD_Network *net);
|
||||
wifi_auth_dialog_show(inst, net);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Open network - connect directly */
|
||||
DBG("Connecting to open network");
|
||||
iwd_network_connect(net);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_dbus.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../e_mod_main.h"
|
||||
|
||||
#define IWD_AGENT_PATH "/net/eiwd/agent"
|
||||
/* Global agent */
|
||||
IWD_Agent *iwd_agent = NULL;
|
||||
|
||||
struct _Iwd_Agent
|
||||
{
|
||||
Eldbus_Connection *conn;
|
||||
Eldbus_Service_Interface *svc;
|
||||
Eldbus_Object *am_obj;
|
||||
Eldbus_Proxy *am_proxy;
|
||||
Iwd_Agent_Passphrase_Cb cb;
|
||||
void *data;
|
||||
Iwd_Agent_Cancel_Cb cancel_cb;
|
||||
void *cancel_data;
|
||||
};
|
||||
/* Forward declarations */
|
||||
static Eldbus_Message *_agent_request_passphrase(const Eldbus_Service_Interface *iface, const Eldbus_Message *msg);
|
||||
static Eldbus_Message *_agent_cancel(const Eldbus_Service_Interface *iface, const Eldbus_Message *msg);
|
||||
static Eldbus_Message *_agent_release(const Eldbus_Service_Interface *iface, const Eldbus_Message *msg);
|
||||
static void _agent_register_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending);
|
||||
static void _agent_unregister(void);
|
||||
|
||||
struct _Iwd_Agent_Request
|
||||
{
|
||||
Iwd_Agent *agent;
|
||||
Eldbus_Message *msg; /* original RequestPassphrase message */
|
||||
};
|
||||
|
||||
static Iwd_Agent *_self = NULL; /* one agent per process is plenty */
|
||||
|
||||
/* ----- Method handlers ------------------------------------------------- */
|
||||
|
||||
static Eldbus_Message *
|
||||
_release_cb(const Eldbus_Service_Interface *iface EINA_UNUSED,
|
||||
const Eldbus_Message *msg)
|
||||
{
|
||||
return eldbus_message_method_return_new(msg);
|
||||
}
|
||||
|
||||
static Eldbus_Message *
|
||||
_cancel_cb(const Eldbus_Service_Interface *iface EINA_UNUSED,
|
||||
const Eldbus_Message *msg)
|
||||
{
|
||||
/* iwd dropped the auth attempt; let the UI tear down its dialog. */
|
||||
const char *reason = NULL;
|
||||
if (!eldbus_message_arguments_get(msg, "s", &reason)) reason = NULL;
|
||||
if (_self && _self->cancel_cb)
|
||||
_self->cancel_cb(_self->cancel_data, reason);
|
||||
return eldbus_message_method_return_new(msg);
|
||||
}
|
||||
|
||||
/* iwd may also call these for EAP networks. We don't have UI for them yet,
|
||||
* so politely refuse — that just fails the connect attempt instead of
|
||||
* getting our agent unregistered. */
|
||||
static Eldbus_Message *
|
||||
_unsupported_cb(const Eldbus_Service_Interface *iface EINA_UNUSED,
|
||||
const Eldbus_Message *msg)
|
||||
{
|
||||
return eldbus_message_error_new(msg,
|
||||
"net.connman.iwd.Agent.Error.Canceled",
|
||||
"Method not supported by this agent");
|
||||
}
|
||||
|
||||
static Eldbus_Message *
|
||||
_request_passphrase_cb(const Eldbus_Service_Interface *iface EINA_UNUSED,
|
||||
const Eldbus_Message *msg)
|
||||
{
|
||||
const char *path = NULL;
|
||||
if (!eldbus_message_arguments_get(msg, "o", &path))
|
||||
return eldbus_message_error_new(msg, "net.connman.iwd.Error.InvalidArgs",
|
||||
"Expected object path");
|
||||
if (!_self || !_self->cb)
|
||||
return eldbus_message_error_new(msg, "net.connman.iwd.Agent.Error.Canceled",
|
||||
"No UI handler");
|
||||
|
||||
Iwd_Agent_Request *req = calloc(1, sizeof(*req));
|
||||
req->agent = _self;
|
||||
req->msg = eldbus_message_ref((Eldbus_Message *)msg);
|
||||
_self->cb(_self->data, req, path);
|
||||
/* Deferred reply: returning NULL keeps the message pending. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const Eldbus_Method _methods[] = {
|
||||
{ "Release", NULL,
|
||||
NULL, _release_cb, 0 },
|
||||
{ "RequestPassphrase",
|
||||
ELDBUS_ARGS({ "o", "network" }),
|
||||
ELDBUS_ARGS({ "s", "passphrase" }),
|
||||
_request_passphrase_cb, 0 },
|
||||
{ "Cancel",
|
||||
ELDBUS_ARGS({ "s", "reason" }),
|
||||
NULL, _cancel_cb, 0 },
|
||||
{ "RequestPrivateKeyPassphrase",
|
||||
ELDBUS_ARGS({ "o", "network" }),
|
||||
ELDBUS_ARGS({ "s", "passphrase" }),
|
||||
_unsupported_cb, 0 },
|
||||
{ "RequestUserNameAndPassword",
|
||||
ELDBUS_ARGS({ "o", "network" }),
|
||||
ELDBUS_ARGS({ "s", "user" }, { "s", "password" }),
|
||||
_unsupported_cb, 0 },
|
||||
{ "RequestUserPassword",
|
||||
ELDBUS_ARGS({ "o", "network" }, { "s", "user" }),
|
||||
ELDBUS_ARGS({ "s", "password" }),
|
||||
_unsupported_cb, 0 },
|
||||
/* Agent interface methods */
|
||||
static const Eldbus_Method agent_methods[] = {
|
||||
{
|
||||
"RequestPassphrase", ELDBUS_ARGS({"o", "network"}),
|
||||
ELDBUS_ARGS({"s", "passphrase"}),
|
||||
_agent_request_passphrase, 0
|
||||
},
|
||||
{
|
||||
"Cancel", ELDBUS_ARGS({"s", "reason"}),
|
||||
NULL,
|
||||
_agent_cancel, 0
|
||||
},
|
||||
{
|
||||
"Release", NULL, NULL,
|
||||
_agent_release, 0
|
||||
},
|
||||
{ NULL, NULL, NULL, NULL, 0 }
|
||||
};
|
||||
|
||||
static const Eldbus_Service_Interface_Desc _iface_desc = {
|
||||
IWD_IFACE_AGENT, _methods, NULL, NULL, NULL, NULL
|
||||
/* Agent interface description */
|
||||
static const Eldbus_Service_Interface_Desc agent_desc = {
|
||||
IWD_AGENT_MANAGER_INTERFACE, agent_methods, NULL, NULL, NULL, NULL
|
||||
};
|
||||
|
||||
/* ----- Reply / cancel from the UI ------------------------------------- */
|
||||
|
||||
void
|
||||
iwd_agent_reply(Iwd_Agent_Request *req, const char *passphrase)
|
||||
/* Initialize agent */
|
||||
Eina_Bool
|
||||
iwd_agent_init(void)
|
||||
{
|
||||
if (!req) return;
|
||||
Eldbus_Message *r = eldbus_message_method_return_new(req->msg);
|
||||
eldbus_message_arguments_append(r, "s", passphrase ? passphrase : "");
|
||||
eldbus_connection_send(req->agent->conn, r, NULL, NULL, -1);
|
||||
eldbus_message_unref(req->msg);
|
||||
free(req);
|
||||
Eldbus_Connection *conn;
|
||||
Eldbus_Object *obj;
|
||||
Eldbus_Proxy *proxy;
|
||||
|
||||
DBG("Initializing iwd agent");
|
||||
|
||||
if (iwd_agent)
|
||||
{
|
||||
WRN("Agent already initialized");
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
conn = iwd_dbus_conn_get();
|
||||
if (!conn)
|
||||
{
|
||||
ERR("No D-Bus connection available");
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
iwd_agent = E_NEW(IWD_Agent, 1);
|
||||
if (!iwd_agent)
|
||||
{
|
||||
ERR("Failed to allocate agent");
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
/* Initialize fields */
|
||||
iwd_agent->manager_obj = NULL;
|
||||
iwd_agent->pending_network_path = NULL;
|
||||
iwd_agent->pending_passphrase = NULL;
|
||||
iwd_agent->pending_msg = NULL;
|
||||
|
||||
/* Register D-Bus service interface */
|
||||
iwd_agent->iface = eldbus_service_interface_register(conn, IWD_AGENT_PATH, &agent_desc);
|
||||
if (!iwd_agent->iface)
|
||||
{
|
||||
ERR("Failed to register agent interface");
|
||||
E_FREE(iwd_agent);
|
||||
iwd_agent = NULL;
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
/* Register agent with iwd daemon (AgentManager is at /net/connman/iwd) */
|
||||
obj = eldbus_object_get(conn, IWD_SERVICE, IWD_DAEMON_PATH);
|
||||
if (!obj)
|
||||
{
|
||||
ERR("Failed to get iwd daemon object");
|
||||
eldbus_service_interface_unregister(iwd_agent->iface);
|
||||
E_FREE(iwd_agent);
|
||||
iwd_agent = NULL;
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
proxy = eldbus_proxy_get(obj, IWD_AGENT_MANAGER_INTERFACE);
|
||||
if (!proxy)
|
||||
{
|
||||
ERR("Failed to get AgentManager proxy");
|
||||
eldbus_object_unref(obj);
|
||||
eldbus_service_interface_unregister(iwd_agent->iface);
|
||||
E_FREE(iwd_agent);
|
||||
iwd_agent = NULL;
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
/* Store object reference to keep it alive during async call */
|
||||
iwd_agent->manager_obj = obj;
|
||||
|
||||
eldbus_proxy_call(proxy, "RegisterAgent", _agent_register_cb, NULL, -1, "o", IWD_AGENT_PATH);
|
||||
|
||||
INF("Agent initialization started");
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
/* Shutdown agent */
|
||||
void
|
||||
iwd_agent_cancel(Iwd_Agent_Request *req)
|
||||
iwd_agent_shutdown(void)
|
||||
{
|
||||
if (!req) return;
|
||||
Eldbus_Message *e = eldbus_message_error_new(req->msg,
|
||||
"net.connman.iwd.Agent.Error.Canceled",
|
||||
"User canceled");
|
||||
eldbus_connection_send(req->agent->conn, e, NULL, NULL, -1);
|
||||
eldbus_message_unref(req->msg);
|
||||
free(req);
|
||||
DBG("Shutting down iwd agent");
|
||||
|
||||
if (!iwd_agent) return;
|
||||
|
||||
_agent_unregister();
|
||||
|
||||
if (iwd_agent->iface)
|
||||
eldbus_service_interface_unregister(iwd_agent->iface);
|
||||
|
||||
if (iwd_agent->manager_obj)
|
||||
eldbus_object_unref(iwd_agent->manager_obj);
|
||||
|
||||
eina_stringshare_del(iwd_agent->pending_network_path);
|
||||
eina_stringshare_del(iwd_agent->pending_passphrase);
|
||||
|
||||
E_FREE(iwd_agent);
|
||||
iwd_agent = NULL;
|
||||
}
|
||||
|
||||
/* ----- Registration with iwd ------------------------------------------ */
|
||||
/* Set passphrase for pending request and send reply */
|
||||
void
|
||||
iwd_agent_set_passphrase(const char *passphrase)
|
||||
{
|
||||
Eldbus_Message *reply;
|
||||
|
||||
if (!iwd_agent) return;
|
||||
if (!iwd_agent->pending_msg)
|
||||
{
|
||||
WRN("No pending passphrase request");
|
||||
return;
|
||||
}
|
||||
|
||||
DBG("Sending passphrase to iwd");
|
||||
|
||||
/* Create reply message */
|
||||
reply = eldbus_message_method_return_new(iwd_agent->pending_msg);
|
||||
if (reply)
|
||||
{
|
||||
eldbus_message_arguments_append(reply, "s", passphrase);
|
||||
eldbus_connection_send(eldbus_service_connection_get(iwd_agent->iface),
|
||||
reply, NULL, NULL, -1);
|
||||
}
|
||||
|
||||
/* Clear pending request */
|
||||
eina_stringshare_del(iwd_agent->pending_network_path);
|
||||
iwd_agent->pending_network_path = NULL;
|
||||
iwd_agent->pending_msg = NULL;
|
||||
|
||||
INF("Passphrase sent to iwd");
|
||||
}
|
||||
|
||||
/* Cancel pending request */
|
||||
void
|
||||
iwd_agent_cancel(void)
|
||||
{
|
||||
Eldbus_Message *reply;
|
||||
|
||||
if (!iwd_agent) return;
|
||||
|
||||
/* Send cancellation reply if there's a pending request */
|
||||
if (iwd_agent->pending_msg)
|
||||
{
|
||||
reply = eldbus_message_error_new(iwd_agent->pending_msg,
|
||||
"net.connman.iwd.Agent.Error.Canceled",
|
||||
"User cancelled");
|
||||
if (reply)
|
||||
{
|
||||
eldbus_connection_send(eldbus_service_connection_get(iwd_agent->iface),
|
||||
reply, NULL, NULL, -1);
|
||||
}
|
||||
}
|
||||
|
||||
eina_stringshare_del(iwd_agent->pending_network_path);
|
||||
eina_stringshare_del(iwd_agent->pending_passphrase);
|
||||
iwd_agent->pending_network_path = NULL;
|
||||
iwd_agent->pending_passphrase = NULL;
|
||||
iwd_agent->pending_msg = NULL;
|
||||
|
||||
DBG("Agent request cancelled");
|
||||
}
|
||||
|
||||
/* Agent registration callback */
|
||||
static void
|
||||
_on_register(void *data EINA_UNUSED, const Eldbus_Message *msg,
|
||||
Eldbus_Pending *p EINA_UNUSED)
|
||||
_agent_register_cb(void *data EINA_UNUSED,
|
||||
const Eldbus_Message *msg,
|
||||
Eldbus_Pending *pending EINA_UNUSED)
|
||||
{
|
||||
const char *en, *em;
|
||||
if (eldbus_message_error_get(msg, &en, &em))
|
||||
fprintf(stderr, "e_iwd: agent register failed: %s: %s\n", en, em);
|
||||
const char *err_name, *err_msg;
|
||||
|
||||
if (eldbus_message_error_get(msg, &err_name, &err_msg))
|
||||
{
|
||||
ERR("Failed to register agent: %s: %s", err_name, err_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
INF("Agent registered with iwd");
|
||||
}
|
||||
|
||||
Iwd_Agent *
|
||||
iwd_agent_new(Eldbus_Connection *conn, Iwd_Agent_Passphrase_Cb cb, void *data)
|
||||
/* Unregister agent */
|
||||
static void
|
||||
_agent_unregister(void)
|
||||
{
|
||||
Iwd_Agent *a = calloc(1, sizeof(*a));
|
||||
if (!a) return NULL;
|
||||
a->conn = conn;
|
||||
a->cb = cb;
|
||||
a->data = data;
|
||||
_self = a;
|
||||
Eldbus_Connection *conn;
|
||||
Eldbus_Object *obj;
|
||||
Eldbus_Proxy *proxy;
|
||||
|
||||
a->svc = eldbus_service_interface_register(conn, IWD_AGENT_PATH, &_iface_desc);
|
||||
if (!a->svc) { free(a); _self = NULL; return NULL; }
|
||||
conn = iwd_dbus_conn_get();
|
||||
if (!conn) return;
|
||||
|
||||
a->am_obj = eldbus_object_get(conn, IWD_BUS_NAME, "/net/connman/iwd");
|
||||
if (a->am_obj)
|
||||
{
|
||||
a->am_proxy = eldbus_proxy_get(a->am_obj, IWD_IFACE_AGENT_MANAGER);
|
||||
if (a->am_proxy)
|
||||
eldbus_proxy_call(a->am_proxy, "RegisterAgent", _on_register, NULL, -1,
|
||||
"o", IWD_AGENT_PATH);
|
||||
}
|
||||
return a;
|
||||
obj = eldbus_object_get(conn, IWD_SERVICE, IWD_DAEMON_PATH);
|
||||
if (!obj) return;
|
||||
|
||||
proxy = eldbus_proxy_get(obj, IWD_AGENT_MANAGER_INTERFACE);
|
||||
if (proxy)
|
||||
{
|
||||
eldbus_proxy_call(proxy, "UnregisterAgent", NULL, NULL, -1, "o", IWD_AGENT_PATH);
|
||||
DBG("Agent unregistered from iwd");
|
||||
}
|
||||
|
||||
eldbus_object_unref(obj);
|
||||
}
|
||||
|
||||
void
|
||||
iwd_agent_set_cancel_cb(Iwd_Agent *a, Iwd_Agent_Cancel_Cb cb, void *data)
|
||||
/* Request passphrase method */
|
||||
static Eldbus_Message *
|
||||
_agent_request_passphrase(const Eldbus_Service_Interface *iface EINA_UNUSED,
|
||||
const Eldbus_Message *msg)
|
||||
{
|
||||
if (!a) return;
|
||||
a->cancel_cb = cb;
|
||||
a->cancel_data = data;
|
||||
const char *network_path;
|
||||
IWD_Network *net;
|
||||
|
||||
if (!eldbus_message_arguments_get(msg, "o", &network_path))
|
||||
{
|
||||
ERR("Failed to get network path from RequestPassphrase");
|
||||
return eldbus_message_error_new(msg, "org.freedesktop.DBus.Error.InvalidArgs", "Invalid arguments");
|
||||
}
|
||||
|
||||
INF("Passphrase requested for network: %s", network_path);
|
||||
|
||||
/* Store network path and message for later reply */
|
||||
eina_stringshare_replace(&iwd_agent->pending_network_path, network_path);
|
||||
iwd_agent->pending_msg = msg;
|
||||
|
||||
/* Find the network */
|
||||
net = iwd_network_find(network_path);
|
||||
if (!net)
|
||||
{
|
||||
ERR("Network not found: %s", network_path);
|
||||
iwd_agent->pending_msg = NULL;
|
||||
return eldbus_message_error_new(msg, "net.connman.iwd.Agent.Error.Canceled", "Network not found");
|
||||
}
|
||||
|
||||
/* Show passphrase dialog - this will eventually call iwd_agent_set_passphrase */
|
||||
/* We need to get the instance - for now, use the first one */
|
||||
if (iwd_mod && iwd_mod->instances)
|
||||
{
|
||||
Instance *inst = eina_list_data_get(iwd_mod->instances);
|
||||
if (inst)
|
||||
{
|
||||
extern void wifi_auth_dialog_show(Instance *inst, IWD_Network *net);
|
||||
wifi_auth_dialog_show(inst, net);
|
||||
}
|
||||
}
|
||||
|
||||
/* Return NULL to indicate we'll reply later (async) */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
iwd_agent_free(Iwd_Agent *a)
|
||||
/* Cancel method */
|
||||
static Eldbus_Message *
|
||||
_agent_cancel(const Eldbus_Service_Interface *iface EINA_UNUSED,
|
||||
const Eldbus_Message *msg)
|
||||
{
|
||||
if (!a) return;
|
||||
if (a->svc) eldbus_service_interface_unregister(a->svc);
|
||||
if (a->am_proxy) eldbus_proxy_unref(a->am_proxy);
|
||||
if (a->am_obj) eldbus_object_unref(a->am_obj);
|
||||
if (_self == a) _self = NULL;
|
||||
free(a);
|
||||
const char *reason;
|
||||
|
||||
if (!eldbus_message_arguments_get(msg, "s", &reason))
|
||||
{
|
||||
WRN("Cancel called with no reason");
|
||||
reason = "unknown";
|
||||
}
|
||||
|
||||
INF("Agent request cancelled: %s", reason);
|
||||
|
||||
iwd_agent_cancel();
|
||||
|
||||
/* TODO: Close passphrase dialog if open (Phase 4) */
|
||||
|
||||
return eldbus_message_method_return_new(msg);
|
||||
}
|
||||
|
||||
/* Release method */
|
||||
static Eldbus_Message *
|
||||
_agent_release(const Eldbus_Service_Interface *iface EINA_UNUSED,
|
||||
const Eldbus_Message *msg)
|
||||
{
|
||||
INF("Agent released by iwd");
|
||||
|
||||
iwd_agent_cancel();
|
||||
|
||||
return eldbus_message_method_return_new(msg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,32 @@
|
|||
#ifndef IWD_AGENT_H
|
||||
#define IWD_AGENT_H
|
||||
|
||||
#include <Eina.h>
|
||||
#include <Eldbus.h>
|
||||
|
||||
typedef struct _Iwd_Agent Iwd_Agent;
|
||||
typedef struct _Iwd_Agent_Request Iwd_Agent_Request;
|
||||
#define IWD_AGENT_PATH "/org/enlightenment/eiwd/agent"
|
||||
|
||||
/* The UI registers a single handler that is called whenever iwd asks for
|
||||
* a passphrase. The handler must eventually call iwd_agent_reply() or
|
||||
* iwd_agent_cancel() with the request token. */
|
||||
typedef void (*Iwd_Agent_Passphrase_Cb)(void *data,
|
||||
Iwd_Agent_Request *req,
|
||||
const char *network_path);
|
||||
/* Agent structure */
|
||||
typedef struct _IWD_Agent
|
||||
{
|
||||
Eldbus_Service_Interface *iface;
|
||||
Eldbus_Object *manager_obj; /* Keep reference to prevent call cancellation */
|
||||
const char *pending_network_path;
|
||||
const char *pending_passphrase;
|
||||
const Eldbus_Message *pending_msg; /* Stored message to reply to */
|
||||
} IWD_Agent;
|
||||
|
||||
/* Fired when iwd issues a Cancel(reason) for the in-flight passphrase
|
||||
* request — the UI should tear down any open auth dialog. */
|
||||
typedef void (*Iwd_Agent_Cancel_Cb)(void *data, const char *reason);
|
||||
/* Global agent */
|
||||
extern IWD_Agent *iwd_agent;
|
||||
|
||||
Iwd_Agent *iwd_agent_new (Eldbus_Connection *conn,
|
||||
Iwd_Agent_Passphrase_Cb cb, void *data);
|
||||
/* Agent management */
|
||||
Eina_Bool iwd_agent_init(void);
|
||||
void iwd_agent_shutdown(void);
|
||||
|
||||
void iwd_agent_set_cancel_cb(Iwd_Agent *a, Iwd_Agent_Cancel_Cb cb, void *data);
|
||||
void iwd_agent_free(Iwd_Agent *a);
|
||||
/* Set passphrase for pending request */
|
||||
void iwd_agent_set_passphrase(const char *passphrase);
|
||||
|
||||
void iwd_agent_reply (Iwd_Agent_Request *req, const char *passphrase);
|
||||
void iwd_agent_cancel(Iwd_Agent_Request *req);
|
||||
/* Cancel pending request */
|
||||
void iwd_agent_cancel(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,161 +1,398 @@
|
|||
#include "iwd_dbus.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../e_mod_main.h"
|
||||
|
||||
#define FDO_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager"
|
||||
/* Global D-Bus context */
|
||||
IWD_DBus *iwd_dbus = NULL;
|
||||
|
||||
struct _Iwd_Dbus
|
||||
/* Forward declarations */
|
||||
static void _iwd_dbus_name_owner_changed_cb(void *data, const char *bus EINA_UNUSED, const char *old_id, const char *new_id);
|
||||
static void _iwd_dbus_managed_objects_get_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending);
|
||||
static void _iwd_dbus_interfaces_added_cb(void *data, const Eldbus_Message *msg);
|
||||
static void _iwd_dbus_interfaces_removed_cb(void *data, const Eldbus_Message *msg);
|
||||
static void _iwd_dbus_connect(void);
|
||||
static void _iwd_dbus_disconnect(void);
|
||||
|
||||
/* Initialize D-Bus connection */
|
||||
Eina_Bool
|
||||
iwd_dbus_init(void)
|
||||
{
|
||||
Eldbus_Connection *conn;
|
||||
Iwd_Dbus_Callbacks cbs;
|
||||
void *data;
|
||||
DBG("Initializing iwd D-Bus connection");
|
||||
|
||||
Eldbus_Object *root_obj;
|
||||
Eldbus_Proxy *root_om;
|
||||
Eldbus_Signal_Handler *sh_added;
|
||||
Eldbus_Signal_Handler *sh_removed;
|
||||
Eina_Bool present;
|
||||
};
|
||||
if (iwd_dbus)
|
||||
{
|
||||
WRN("D-Bus already initialized");
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
Eldbus_Connection *
|
||||
iwd_dbus_conn(const Iwd_Dbus *d) { return d ? d->conn : NULL; }
|
||||
iwd_dbus = E_NEW(IWD_DBus, 1);
|
||||
if (!iwd_dbus)
|
||||
{
|
||||
ERR("Failed to allocate D-Bus context");
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
/* Walk the a{oa{sa{sv}}} reply from GetManagedObjects, emitting iface_added
|
||||
* for every (path, interface) pair. */
|
||||
static void
|
||||
_emit_managed(Iwd_Dbus *d, Eldbus_Message_Iter *objects)
|
||||
{
|
||||
Eldbus_Message_Iter *entry;
|
||||
while (eldbus_message_iter_get_and_next(objects, 'e', &entry))
|
||||
{
|
||||
const char *path;
|
||||
Eldbus_Message_Iter *ifaces;
|
||||
if (!eldbus_message_iter_arguments_get(entry, "oa{sa{sv}}", &path, &ifaces))
|
||||
continue;
|
||||
/* Connect to system bus */
|
||||
iwd_dbus->conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM);
|
||||
if (!iwd_dbus->conn)
|
||||
{
|
||||
ERR("Failed to connect to system bus");
|
||||
E_FREE(iwd_dbus);
|
||||
iwd_dbus = NULL;
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
Eldbus_Message_Iter *iface_entry;
|
||||
while (eldbus_message_iter_get_and_next(ifaces, 'e', &iface_entry))
|
||||
{
|
||||
const char *iface;
|
||||
Eldbus_Message_Iter *props;
|
||||
if (!eldbus_message_iter_arguments_get(iface_entry, "sa{sv}", &iface, &props))
|
||||
continue;
|
||||
if (d->cbs.iface_added)
|
||||
d->cbs.iface_added(d->data, path, iface, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_on_get_managed(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED)
|
||||
{
|
||||
Iwd_Dbus *d = data;
|
||||
if (eldbus_message_error_get(msg, NULL, NULL)) return;
|
||||
Eldbus_Message_Iter *objects;
|
||||
if (!eldbus_message_arguments_get(msg, "a{oa{sa{sv}}}", &objects)) return;
|
||||
_emit_managed(d, objects);
|
||||
}
|
||||
|
||||
static void
|
||||
_on_iface_added(void *data, const Eldbus_Message *msg)
|
||||
{
|
||||
Iwd_Dbus *d = data;
|
||||
const char *path;
|
||||
Eldbus_Message_Iter *ifaces, *iface_entry;
|
||||
if (!eldbus_message_arguments_get(msg, "oa{sa{sv}}", &path, &ifaces))
|
||||
return;
|
||||
while (eldbus_message_iter_get_and_next(ifaces, 'e', &iface_entry))
|
||||
{
|
||||
const char *iface;
|
||||
Eldbus_Message_Iter *props;
|
||||
if (!eldbus_message_iter_arguments_get(iface_entry, "sa{sv}", &iface, &props))
|
||||
continue;
|
||||
if (d->cbs.iface_added)
|
||||
d->cbs.iface_added(d->data, path, iface, props);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_on_iface_removed(void *data, const Eldbus_Message *msg)
|
||||
{
|
||||
Iwd_Dbus *d = data;
|
||||
const char *path;
|
||||
Eldbus_Message_Iter *ifaces;
|
||||
if (!eldbus_message_arguments_get(msg, "oas", &path, &ifaces))
|
||||
return;
|
||||
const char *iface;
|
||||
while (eldbus_message_iter_get_and_next(ifaces, 's', &iface))
|
||||
{
|
||||
if (d->cbs.iface_removed)
|
||||
d->cbs.iface_removed(d->data, path, iface);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_bind_root(Iwd_Dbus *d)
|
||||
{
|
||||
if (d->root_om) return;
|
||||
d->root_obj = eldbus_object_get(d->conn, IWD_BUS_NAME, "/");
|
||||
if (!d->root_obj) return;
|
||||
d->root_om = eldbus_proxy_get(d->root_obj, FDO_OBJECT_MANAGER);
|
||||
if (!d->root_om) return;
|
||||
|
||||
d->sh_added = eldbus_proxy_signal_handler_add(d->root_om, "InterfacesAdded",
|
||||
_on_iface_added, d);
|
||||
d->sh_removed = eldbus_proxy_signal_handler_add(d->root_om, "InterfacesRemoved",
|
||||
_on_iface_removed, d);
|
||||
eldbus_proxy_call(d->root_om, "GetManagedObjects", _on_get_managed, d, -1, "");
|
||||
}
|
||||
|
||||
static void
|
||||
_unbind_root(Iwd_Dbus *d)
|
||||
{
|
||||
if (d->sh_added) { eldbus_signal_handler_del(d->sh_added); d->sh_added = NULL; }
|
||||
if (d->sh_removed) { eldbus_signal_handler_del(d->sh_removed); d->sh_removed = NULL; }
|
||||
if (d->root_om) { eldbus_proxy_unref(d->root_om); d->root_om = NULL; }
|
||||
if (d->root_obj) { eldbus_object_unref(d->root_obj); d->root_obj = NULL; }
|
||||
}
|
||||
|
||||
static void
|
||||
_on_name_owner(void *data, const char *bus EINA_UNUSED, const char *old_id, const char *new_id)
|
||||
{
|
||||
Iwd_Dbus *d = data;
|
||||
Eina_Bool now = (new_id && new_id[0]);
|
||||
Eina_Bool was = (old_id && old_id[0]);
|
||||
|
||||
if (now && !d->present)
|
||||
{
|
||||
d->present = EINA_TRUE;
|
||||
_bind_root(d);
|
||||
if (d->cbs.name_appeared) d->cbs.name_appeared(d->data);
|
||||
}
|
||||
else if (!now && (was || d->present))
|
||||
{
|
||||
d->present = EINA_FALSE;
|
||||
_unbind_root(d);
|
||||
if (d->cbs.name_vanished) d->cbs.name_vanished(d->data);
|
||||
}
|
||||
}
|
||||
|
||||
Iwd_Dbus *
|
||||
iwd_dbus_new(Eldbus_Connection *conn, const Iwd_Dbus_Callbacks *cbs, void *data)
|
||||
{
|
||||
Iwd_Dbus *d = calloc(1, sizeof(*d));
|
||||
if (!d) return NULL;
|
||||
d->conn = conn;
|
||||
if (cbs) d->cbs = *cbs;
|
||||
d->data = data;
|
||||
|
||||
eldbus_name_owner_changed_callback_add(conn, IWD_BUS_NAME,
|
||||
_on_name_owner, d, EINA_TRUE);
|
||||
return d;
|
||||
/* Monitor iwd daemon availability */
|
||||
eldbus_name_owner_changed_callback_add(iwd_dbus->conn,
|
||||
IWD_SERVICE,
|
||||
_iwd_dbus_name_owner_changed_cb,
|
||||
NULL,
|
||||
EINA_TRUE);
|
||||
|
||||
/* Try to connect to iwd */
|
||||
_iwd_dbus_connect();
|
||||
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
/* Shutdown D-Bus connection */
|
||||
void
|
||||
iwd_dbus_free(Iwd_Dbus *d)
|
||||
iwd_dbus_shutdown(void)
|
||||
{
|
||||
if (!d) return;
|
||||
eldbus_name_owner_changed_callback_del(d->conn, IWD_BUS_NAME, _on_name_owner, d);
|
||||
_unbind_root(d);
|
||||
free(d);
|
||||
DBG("Shutting down iwd D-Bus connection");
|
||||
|
||||
if (!iwd_dbus) return;
|
||||
|
||||
_iwd_dbus_disconnect();
|
||||
|
||||
/* Unregister name owner changed callback */
|
||||
eldbus_name_owner_changed_callback_del(iwd_dbus->conn, IWD_SERVICE,
|
||||
_iwd_dbus_name_owner_changed_cb, NULL);
|
||||
|
||||
if (iwd_dbus->conn)
|
||||
eldbus_connection_unref(iwd_dbus->conn);
|
||||
|
||||
E_FREE(iwd_dbus);
|
||||
iwd_dbus = NULL;
|
||||
}
|
||||
|
||||
/* Check if connected to iwd */
|
||||
Eina_Bool
|
||||
iwd_dbus_is_connected(void)
|
||||
{
|
||||
return (iwd_dbus && iwd_dbus->connected);
|
||||
}
|
||||
|
||||
/* Get D-Bus connection */
|
||||
Eldbus_Connection *
|
||||
iwd_dbus_conn_get(void)
|
||||
{
|
||||
return iwd_dbus ? iwd_dbus->conn : NULL;
|
||||
}
|
||||
|
||||
/* Request managed objects from iwd */
|
||||
void
|
||||
iwd_dbus_get_managed_objects(void)
|
||||
{
|
||||
Eldbus_Proxy *proxy;
|
||||
|
||||
if (!iwd_dbus || !iwd_dbus->manager_obj) return;
|
||||
|
||||
DBG("Requesting managed objects from iwd");
|
||||
|
||||
proxy = eldbus_proxy_get(iwd_dbus->manager_obj, DBUS_OBJECT_MANAGER_INTERFACE);
|
||||
if (!proxy)
|
||||
{
|
||||
ERR("Failed to get ObjectManager proxy");
|
||||
return;
|
||||
}
|
||||
|
||||
eldbus_proxy_call(proxy, "GetManagedObjects",
|
||||
_iwd_dbus_managed_objects_get_cb,
|
||||
NULL, -1, "");
|
||||
}
|
||||
|
||||
/* Connect to iwd daemon */
|
||||
static void
|
||||
_iwd_dbus_connect(void)
|
||||
{
|
||||
if (!iwd_dbus || !iwd_dbus->conn) return;
|
||||
|
||||
DBG("Connecting to iwd daemon");
|
||||
|
||||
/* Create manager object */
|
||||
iwd_dbus->manager_obj = eldbus_object_get(iwd_dbus->conn, IWD_SERVICE, IWD_MANAGER_PATH);
|
||||
if (!iwd_dbus->manager_obj)
|
||||
{
|
||||
ERR("Failed to get manager object");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Subscribe to ObjectManager signals */
|
||||
iwd_dbus->interfaces_added =
|
||||
eldbus_proxy_signal_handler_add(
|
||||
eldbus_proxy_get(iwd_dbus->manager_obj, DBUS_OBJECT_MANAGER_INTERFACE),
|
||||
"InterfacesAdded",
|
||||
_iwd_dbus_interfaces_added_cb,
|
||||
NULL);
|
||||
|
||||
iwd_dbus->interfaces_removed =
|
||||
eldbus_proxy_signal_handler_add(
|
||||
eldbus_proxy_get(iwd_dbus->manager_obj, DBUS_OBJECT_MANAGER_INTERFACE),
|
||||
"InterfacesRemoved",
|
||||
_iwd_dbus_interfaces_removed_cb,
|
||||
NULL);
|
||||
|
||||
iwd_dbus->connected = EINA_TRUE;
|
||||
INF("Connected to iwd daemon");
|
||||
|
||||
/* Get initial state */
|
||||
iwd_dbus_get_managed_objects();
|
||||
}
|
||||
|
||||
/* Disconnect from iwd daemon */
|
||||
static void
|
||||
_iwd_dbus_disconnect(void)
|
||||
{
|
||||
if (!iwd_dbus) return;
|
||||
|
||||
DBG("Disconnecting from iwd daemon");
|
||||
|
||||
/* Unref the object first - this will clean up associated signal handlers */
|
||||
if (iwd_dbus->manager_obj)
|
||||
{
|
||||
eldbus_object_unref(iwd_dbus->manager_obj);
|
||||
iwd_dbus->manager_obj = NULL;
|
||||
}
|
||||
|
||||
/* Clear handler pointers (they're already freed by object unref) */
|
||||
iwd_dbus->interfaces_added = NULL;
|
||||
iwd_dbus->interfaces_removed = NULL;
|
||||
|
||||
iwd_dbus->connected = EINA_FALSE;
|
||||
}
|
||||
|
||||
/* Name owner changed callback */
|
||||
static void
|
||||
_iwd_dbus_name_owner_changed_cb(void *data EINA_UNUSED,
|
||||
const char *bus EINA_UNUSED,
|
||||
const char *old_id,
|
||||
const char *new_id)
|
||||
{
|
||||
DBG("iwd name owner changed: old='%s' new='%s'", old_id, new_id);
|
||||
|
||||
if (new_id && new_id[0])
|
||||
{
|
||||
/* iwd daemon started */
|
||||
INF("iwd daemon started - reconnecting");
|
||||
_iwd_dbus_connect();
|
||||
|
||||
/* Re-register agent */
|
||||
extern Eina_Bool iwd_agent_init(void);
|
||||
iwd_agent_init();
|
||||
|
||||
/* Update state */
|
||||
extern void iwd_state_set(IWD_State state);
|
||||
extern IWD_State iwd_state_get(void);
|
||||
if (iwd_state_get() == IWD_STATE_ERROR)
|
||||
{
|
||||
iwd_state_set(IWD_STATE_IDLE);
|
||||
}
|
||||
}
|
||||
else if (old_id && old_id[0])
|
||||
{
|
||||
/* iwd daemon stopped */
|
||||
WRN("iwd daemon stopped");
|
||||
_iwd_dbus_disconnect();
|
||||
|
||||
/* Set error state */
|
||||
extern void iwd_state_set(IWD_State state);
|
||||
iwd_state_set(IWD_STATE_ERROR);
|
||||
|
||||
/* Show error dialog */
|
||||
e_util_dialog_show("IWD Wi-Fi Error",
|
||||
"Wi-Fi daemon (iwd) has stopped.<br>"
|
||||
"Please restart the iwd service.");
|
||||
}
|
||||
}
|
||||
|
||||
/* Managed objects callback */
|
||||
static void
|
||||
_iwd_dbus_managed_objects_get_cb(void *data EINA_UNUSED,
|
||||
const Eldbus_Message *msg,
|
||||
Eldbus_Pending *pending EINA_UNUSED)
|
||||
{
|
||||
Eldbus_Message_Iter *array, *dict_entry;
|
||||
const char *err_name, *err_msg;
|
||||
|
||||
if (eldbus_message_error_get(msg, &err_name, &err_msg))
|
||||
{
|
||||
ERR("Failed to get managed objects: %s: %s", err_name, err_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eldbus_message_arguments_get(msg, "a{oa{sa{sv}}}", &array))
|
||||
{
|
||||
ERR("Failed to parse GetManagedObjects reply");
|
||||
return;
|
||||
}
|
||||
|
||||
DBG("Processing managed objects from iwd");
|
||||
|
||||
while (eldbus_message_iter_get_and_next(array, 'e', &dict_entry))
|
||||
{
|
||||
Eldbus_Message_Iter *interfaces;
|
||||
const char *path;
|
||||
|
||||
eldbus_message_iter_arguments_get(dict_entry, "o", &path);
|
||||
eldbus_message_iter_arguments_get(dict_entry, "a{sa{sv}}", &interfaces);
|
||||
|
||||
DBG(" Object: %s", path);
|
||||
|
||||
/* Parse interfaces and create device/network objects */
|
||||
Eldbus_Message_Iter *iface_entry;
|
||||
Eina_Bool has_device = EINA_FALSE;
|
||||
Eina_Bool has_station = EINA_FALSE;
|
||||
Eina_Bool has_network = EINA_FALSE;
|
||||
|
||||
while (eldbus_message_iter_get_and_next(interfaces, 'e', &iface_entry))
|
||||
{
|
||||
const char *iface_name;
|
||||
Eldbus_Message_Iter *properties;
|
||||
|
||||
eldbus_message_iter_arguments_get(iface_entry, "s", &iface_name);
|
||||
eldbus_message_iter_arguments_get(iface_entry, "a{sv}", &properties);
|
||||
|
||||
DBG(" Interface: %s", iface_name);
|
||||
|
||||
if (strcmp(iface_name, IWD_DEVICE_INTERFACE) == 0)
|
||||
has_device = EINA_TRUE;
|
||||
else if (strcmp(iface_name, IWD_STATION_INTERFACE) == 0)
|
||||
has_station = EINA_TRUE;
|
||||
else if (strcmp(iface_name, IWD_NETWORK_INTERFACE) == 0)
|
||||
has_network = EINA_TRUE;
|
||||
}
|
||||
|
||||
/* Create device if it has Device and Station interfaces */
|
||||
if (has_device && has_station)
|
||||
{
|
||||
extern IWD_Device *iwd_device_new(const char *path);
|
||||
IWD_Device *dev = iwd_device_new(path);
|
||||
if (dev)
|
||||
{
|
||||
/* Parse properties - re-iterate to get them */
|
||||
eldbus_message_iter_arguments_get(dict_entry, "o", &path);
|
||||
eldbus_message_iter_arguments_get(dict_entry, "a{sa{sv}}", &interfaces);
|
||||
|
||||
while (eldbus_message_iter_get_and_next(interfaces, 'e', &iface_entry))
|
||||
{
|
||||
const char *iface_name;
|
||||
Eldbus_Message_Iter *properties;
|
||||
|
||||
eldbus_message_iter_arguments_get(iface_entry, "s", &iface_name);
|
||||
eldbus_message_iter_arguments_get(iface_entry, "a{sv}", &properties);
|
||||
|
||||
if (strcmp(iface_name, IWD_DEVICE_INTERFACE) == 0 ||
|
||||
strcmp(iface_name, IWD_STATION_INTERFACE) == 0)
|
||||
{
|
||||
extern void _device_parse_properties(IWD_Device *dev, Eldbus_Message_Iter *properties);
|
||||
/* We need to expose the parse function or call it via a public function */
|
||||
/* For now, properties will be updated via PropertyChanged signals */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Create network if it has Network interface */
|
||||
if (has_network)
|
||||
{
|
||||
extern IWD_Network *iwd_network_new(const char *path);
|
||||
iwd_network_new(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Interfaces added callback */
|
||||
static void
|
||||
_iwd_dbus_interfaces_added_cb(void *data EINA_UNUSED,
|
||||
const Eldbus_Message *msg)
|
||||
{
|
||||
const char *path;
|
||||
Eldbus_Message_Iter *interfaces;
|
||||
|
||||
if (!eldbus_message_arguments_get(msg, "oa{sa{sv}}", &path, &interfaces))
|
||||
{
|
||||
ERR("Failed to parse InterfacesAdded signal");
|
||||
return;
|
||||
}
|
||||
|
||||
DBG("Interfaces added at: %s", path);
|
||||
|
||||
/* Check what interfaces were added */
|
||||
Eldbus_Message_Iter *iface_entry;
|
||||
Eina_Bool has_device = EINA_FALSE;
|
||||
Eina_Bool has_station = EINA_FALSE;
|
||||
Eina_Bool has_network = EINA_FALSE;
|
||||
|
||||
while (eldbus_message_iter_get_and_next(interfaces, 'e', &iface_entry))
|
||||
{
|
||||
const char *iface_name;
|
||||
Eldbus_Message_Iter *properties;
|
||||
|
||||
eldbus_message_iter_arguments_get(iface_entry, "s", &iface_name);
|
||||
eldbus_message_iter_arguments_get(iface_entry, "a{sv}", &properties);
|
||||
|
||||
if (strcmp(iface_name, IWD_DEVICE_INTERFACE) == 0)
|
||||
has_device = EINA_TRUE;
|
||||
else if (strcmp(iface_name, IWD_STATION_INTERFACE) == 0)
|
||||
has_station = EINA_TRUE;
|
||||
else if (strcmp(iface_name, IWD_NETWORK_INTERFACE) == 0)
|
||||
has_network = EINA_TRUE;
|
||||
}
|
||||
|
||||
if (has_device && has_station)
|
||||
{
|
||||
extern IWD_Device *iwd_device_new(const char *path);
|
||||
iwd_device_new(path);
|
||||
}
|
||||
|
||||
if (has_network)
|
||||
{
|
||||
extern IWD_Network *iwd_network_new(const char *path);
|
||||
iwd_network_new(path);
|
||||
}
|
||||
}
|
||||
|
||||
/* Interfaces removed callback */
|
||||
static void
|
||||
_iwd_dbus_interfaces_removed_cb(void *data EINA_UNUSED,
|
||||
const Eldbus_Message *msg)
|
||||
{
|
||||
const char *path;
|
||||
Eldbus_Message_Iter *interfaces;
|
||||
|
||||
if (!eldbus_message_arguments_get(msg, "oas", &path, &interfaces))
|
||||
{
|
||||
ERR("Failed to parse InterfacesRemoved signal");
|
||||
return;
|
||||
}
|
||||
|
||||
DBG("Interfaces removed at: %s", path);
|
||||
|
||||
/* Check if we should remove device or network */
|
||||
extern IWD_Device *iwd_device_find(const char *path);
|
||||
extern IWD_Network *iwd_network_find(const char *path);
|
||||
extern void iwd_device_free(IWD_Device *dev);
|
||||
extern void iwd_network_free(IWD_Network *net);
|
||||
|
||||
IWD_Device *dev = iwd_device_find(path);
|
||||
if (dev)
|
||||
{
|
||||
iwd_device_free(dev);
|
||||
}
|
||||
|
||||
IWD_Network *net = iwd_network_find(path);
|
||||
if (net)
|
||||
{
|
||||
iwd_network_free(net);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,50 @@
|
|||
#ifndef IWD_DBUS_H
|
||||
#define IWD_DBUS_H
|
||||
|
||||
#include <Eldbus.h>
|
||||
#include <Eina.h>
|
||||
#include <Eldbus.h>
|
||||
|
||||
#define IWD_BUS_NAME "net.connman.iwd"
|
||||
#define IWD_IFACE_ADAPTER "net.connman.iwd.Adapter"
|
||||
#define IWD_IFACE_DEVICE "net.connman.iwd.Device"
|
||||
#define IWD_IFACE_STATION "net.connman.iwd.Station"
|
||||
#define IWD_IFACE_NETWORK "net.connman.iwd.Network"
|
||||
#define IWD_IFACE_KNOWN_NETWORK "net.connman.iwd.KnownNetwork"
|
||||
#define IWD_IFACE_AGENT_MANAGER "net.connman.iwd.AgentManager"
|
||||
#define IWD_IFACE_AGENT "net.connman.iwd.Agent"
|
||||
/* iwd D-Bus service and interfaces */
|
||||
#define IWD_SERVICE "net.connman.iwd"
|
||||
#define IWD_MANAGER_PATH "/"
|
||||
#define IWD_DAEMON_PATH "/net/connman/iwd"
|
||||
#define IWD_MANAGER_INTERFACE "net.connman.iwd.Manager"
|
||||
#define IWD_ADAPTER_INTERFACE "net.connman.iwd.Adapter"
|
||||
#define IWD_DEVICE_INTERFACE "net.connman.iwd.Device"
|
||||
#define IWD_STATION_INTERFACE "net.connman.iwd.Station"
|
||||
#define IWD_NETWORK_INTERFACE "net.connman.iwd.Network"
|
||||
#define IWD_KNOWN_NETWORK_INTERFACE "net.connman.iwd.KnownNetwork"
|
||||
#define IWD_AGENT_MANAGER_INTERFACE "net.connman.iwd.AgentManager"
|
||||
|
||||
typedef struct _Iwd_Dbus Iwd_Dbus;
|
||||
#define DBUS_OBJECT_MANAGER_INTERFACE "org.freedesktop.DBus.ObjectManager"
|
||||
#define DBUS_PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties"
|
||||
|
||||
/* Callbacks the consumer (iwd_manager) registers to react to bus state. */
|
||||
typedef struct _Iwd_Dbus_Callbacks
|
||||
/* D-Bus context */
|
||||
typedef struct _IWD_DBus
|
||||
{
|
||||
void (*name_appeared) (void *data);
|
||||
void (*name_vanished) (void *data);
|
||||
/* iface_props is an iter positioned on a{sv} for the given interface. */
|
||||
void (*iface_added) (void *data, const char *path, const char *iface,
|
||||
Eldbus_Message_Iter *iface_props);
|
||||
void (*iface_removed) (void *data, const char *path, const char *iface);
|
||||
} Iwd_Dbus_Callbacks;
|
||||
Eldbus_Connection *conn;
|
||||
Eldbus_Object *manager_obj;
|
||||
Eldbus_Proxy *manager_proxy;
|
||||
Eldbus_Signal_Handler *interfaces_added;
|
||||
Eldbus_Signal_Handler *interfaces_removed;
|
||||
|
||||
Iwd_Dbus *iwd_dbus_new (Eldbus_Connection *conn,
|
||||
const Iwd_Dbus_Callbacks *cbs, void *data);
|
||||
void iwd_dbus_free(Iwd_Dbus *d);
|
||||
Eina_Bool connected;
|
||||
} IWD_DBus;
|
||||
|
||||
Eldbus_Connection *iwd_dbus_conn(const Iwd_Dbus *d);
|
||||
/* Global D-Bus context */
|
||||
extern IWD_DBus *iwd_dbus;
|
||||
|
||||
/* Initialization and shutdown */
|
||||
Eina_Bool iwd_dbus_init(void);
|
||||
void iwd_dbus_shutdown(void);
|
||||
|
||||
/* Connection state */
|
||||
Eina_Bool iwd_dbus_is_connected(void);
|
||||
|
||||
/* Helper to get D-Bus connection */
|
||||
Eldbus_Connection *iwd_dbus_conn_get(void);
|
||||
|
||||
/* Get managed objects */
|
||||
void iwd_dbus_get_managed_objects(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,220 +1,290 @@
|
|||
#include "iwd_device.h"
|
||||
#include "iwd_dbus.h"
|
||||
#include "iwd_props.h"
|
||||
#include "iwd_manager.h"
|
||||
#include "iwd_network.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../e_mod_main.h"
|
||||
|
||||
static void _refresh_signals(Iwd_Device *d);
|
||||
/* Global device list */
|
||||
Eina_List *iwd_devices = NULL;
|
||||
|
||||
static Iwd_Station_State
|
||||
_state_from_str(const char *s)
|
||||
/* Forward declarations */
|
||||
static void _device_properties_changed_cb(void *data, const Eldbus_Message *msg);
|
||||
static void _device_parse_properties(IWD_Device *dev, Eldbus_Message_Iter *properties);
|
||||
|
||||
/* Create new device */
|
||||
IWD_Device *
|
||||
iwd_device_new(const char *path)
|
||||
{
|
||||
if (!s) return IWD_STATION_DISCONNECTED;
|
||||
if (!strcmp(s, "connected")) return IWD_STATION_CONNECTED;
|
||||
if (!strcmp(s, "connecting")) return IWD_STATION_CONNECTING;
|
||||
if (!strcmp(s, "disconnecting")) return IWD_STATION_DISCONNECTING;
|
||||
if (!strcmp(s, "roaming")) return IWD_STATION_ROAMING;
|
||||
return IWD_STATION_DISCONNECTED;
|
||||
}
|
||||
|
||||
static void
|
||||
_dev_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v)
|
||||
{
|
||||
Iwd_Device *d = data;
|
||||
if (!strcmp(key, "Name")) { free(d->name); d->name = iwd_props_str_dup(v); }
|
||||
else if (!strcmp(key, "Address")) { free(d->address); d->address = iwd_props_str_dup(v); }
|
||||
else if (!strcmp(key, "Adapter")) { free(d->adapter_path); d->adapter_path = iwd_props_str_dup(v); }
|
||||
else if (!strcmp(key, "Powered")) { d->powered = iwd_props_bool(v); }
|
||||
}
|
||||
|
||||
static void
|
||||
_sta_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v)
|
||||
{
|
||||
Iwd_Device *d = data;
|
||||
if (!strcmp(key, "State"))
|
||||
{
|
||||
char *s = iwd_props_str_dup(v);
|
||||
d->station_state = _state_from_str(s);
|
||||
free(s);
|
||||
}
|
||||
else if (!strcmp(key, "Scanning"))
|
||||
{
|
||||
Eina_Bool was = d->scanning;
|
||||
d->scanning = iwd_props_bool(v);
|
||||
/* When a scan finishes, ask iwd for the ranked list with RSSI. */
|
||||
if (was && !d->scanning) _refresh_signals(d);
|
||||
}
|
||||
else if (!strcmp(key, "ConnectedNetwork")) { free(d->connected_network); d->connected_network = iwd_props_str_dup(v); }
|
||||
IWD_Device *dev;
|
||||
Eldbus_Connection *conn;
|
||||
Eldbus_Object *obj;
|
||||
|
||||
if (!path) return NULL;
|
||||
|
||||
conn = iwd_dbus_conn_get();
|
||||
if (!conn) return NULL;
|
||||
|
||||
/* Check if device already exists */
|
||||
dev = iwd_device_find(path);
|
||||
if (dev)
|
||||
{
|
||||
DBG("Device already exists: %s", path);
|
||||
return dev;
|
||||
}
|
||||
|
||||
DBG("Creating new device: %s", path);
|
||||
|
||||
dev = E_NEW(IWD_Device, 1);
|
||||
if (!dev) return NULL;
|
||||
|
||||
dev->path = eina_stringshare_add(path);
|
||||
|
||||
/* Create D-Bus object */
|
||||
obj = eldbus_object_get(conn, IWD_SERVICE, path);
|
||||
if (!obj)
|
||||
{
|
||||
ERR("Failed to get D-Bus object for device");
|
||||
eina_stringshare_del(dev->path);
|
||||
E_FREE(dev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get proxies */
|
||||
dev->device_proxy = eldbus_proxy_get(obj, IWD_DEVICE_INTERFACE);
|
||||
dev->station_proxy = eldbus_proxy_get(obj, IWD_STATION_INTERFACE);
|
||||
|
||||
/* Subscribe to property changes */
|
||||
dev->properties_changed =
|
||||
eldbus_proxy_signal_handler_add(dev->station_proxy,
|
||||
"PropertiesChanged",
|
||||
_device_properties_changed_cb,
|
||||
dev);
|
||||
|
||||
/* Add to global list */
|
||||
iwd_devices = eina_list_append(iwd_devices, dev);
|
||||
|
||||
INF("Created device: %s", path);
|
||||
|
||||
eldbus_object_unref(obj);
|
||||
return dev;
|
||||
}
|
||||
|
||||
/* Free device */
|
||||
void
|
||||
iwd_device_apply_device_props(Iwd_Device *d, Eldbus_Message_Iter *props)
|
||||
iwd_device_free(IWD_Device *dev)
|
||||
{
|
||||
iwd_props_for_each(props, _dev_prop_cb, d);
|
||||
if (!dev) return;
|
||||
|
||||
DBG("Freeing device: %s", dev->path);
|
||||
|
||||
iwd_devices = eina_list_remove(iwd_devices, dev);
|
||||
|
||||
if (dev->properties_changed)
|
||||
eldbus_signal_handler_del(dev->properties_changed);
|
||||
|
||||
eina_stringshare_del(dev->path);
|
||||
eina_stringshare_del(dev->name);
|
||||
eina_stringshare_del(dev->address);
|
||||
eina_stringshare_del(dev->adapter_path);
|
||||
eina_stringshare_del(dev->mode);
|
||||
eina_stringshare_del(dev->state);
|
||||
eina_stringshare_del(dev->connected_network);
|
||||
|
||||
E_FREE(dev);
|
||||
}
|
||||
|
||||
/* Find device by path */
|
||||
IWD_Device *
|
||||
iwd_device_find(const char *path)
|
||||
{
|
||||
Eina_List *l;
|
||||
IWD_Device *dev;
|
||||
|
||||
if (!path) return NULL;
|
||||
|
||||
EINA_LIST_FOREACH(iwd_devices, l, dev)
|
||||
{
|
||||
if (dev->path && strcmp(dev->path, path) == 0)
|
||||
return dev;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Trigger scan */
|
||||
void
|
||||
iwd_device_apply_station_props(Iwd_Device *d, Eldbus_Message_Iter *props)
|
||||
iwd_device_scan(IWD_Device *dev)
|
||||
{
|
||||
d->has_station = EINA_TRUE;
|
||||
iwd_props_for_each(props, _sta_prop_cb, d);
|
||||
if (!dev || !dev->station_proxy)
|
||||
{
|
||||
ERR("Invalid device for scan");
|
||||
return;
|
||||
}
|
||||
|
||||
DBG("Triggering scan on device: %s", dev->name ? dev->name : dev->path);
|
||||
|
||||
eldbus_proxy_call(dev->station_proxy, "Scan", NULL, NULL, -1, "");
|
||||
}
|
||||
|
||||
/* org.freedesktop.DBus.Properties.PropertiesChanged: (s, a{sv}, as) */
|
||||
static void
|
||||
_on_dev_props_changed(void *data, const Eldbus_Message *msg)
|
||||
/* Disconnect from network */
|
||||
void
|
||||
iwd_device_disconnect(IWD_Device *dev)
|
||||
{
|
||||
Iwd_Device *d = data;
|
||||
const char *iface;
|
||||
if (!dev || !dev->station_proxy)
|
||||
{
|
||||
ERR("Invalid device for disconnect");
|
||||
return;
|
||||
}
|
||||
|
||||
DBG("Disconnecting device: %s", dev->name ? dev->name : dev->path);
|
||||
|
||||
eldbus_proxy_call(dev->station_proxy, "Disconnect", NULL, NULL, -1, "");
|
||||
}
|
||||
|
||||
/* Connect to hidden network */
|
||||
void
|
||||
iwd_device_connect_hidden(IWD_Device *dev, const char *ssid)
|
||||
{
|
||||
if (!dev || !dev->station_proxy || !ssid)
|
||||
{
|
||||
ERR("Invalid parameters for hidden network connect");
|
||||
return;
|
||||
}
|
||||
|
||||
DBG("Connecting to hidden network: %s", ssid);
|
||||
|
||||
eldbus_proxy_call(dev->station_proxy, "ConnectHiddenNetwork",
|
||||
NULL, NULL, -1, "s", ssid);
|
||||
}
|
||||
|
||||
/* Get devices list */
|
||||
Eina_List *
|
||||
iwd_devices_get(void)
|
||||
{
|
||||
return iwd_devices;
|
||||
}
|
||||
|
||||
/* Initialize device subsystem */
|
||||
void
|
||||
iwd_device_init(void)
|
||||
{
|
||||
DBG("Initializing device subsystem");
|
||||
/* Devices will be populated from ObjectManager signals */
|
||||
}
|
||||
|
||||
/* Shutdown device subsystem */
|
||||
void
|
||||
iwd_device_shutdown(void)
|
||||
{
|
||||
IWD_Device *dev;
|
||||
|
||||
DBG("Shutting down device subsystem");
|
||||
|
||||
EINA_LIST_FREE(iwd_devices, dev)
|
||||
iwd_device_free(dev);
|
||||
}
|
||||
|
||||
/* Properties changed callback */
|
||||
static void
|
||||
_device_properties_changed_cb(void *data,
|
||||
const Eldbus_Message *msg)
|
||||
{
|
||||
IWD_Device *dev = data;
|
||||
const char *interface;
|
||||
Eldbus_Message_Iter *changed, *invalidated;
|
||||
if (!eldbus_message_arguments_get(msg, "sa{sv}as", &iface, &changed, &invalidated))
|
||||
return;
|
||||
if (strcmp(iface, IWD_IFACE_DEVICE) != 0) return;
|
||||
iwd_props_for_each(changed, _dev_prop_cb, d);
|
||||
if (d->manager) iwd_manager_notify(d->manager);
|
||||
|
||||
if (!eldbus_message_arguments_get(msg, "sa{sv}as", &interface, &changed, &invalidated))
|
||||
{
|
||||
ERR("Failed to parse PropertiesChanged signal");
|
||||
return;
|
||||
}
|
||||
|
||||
DBG("Properties changed for device %s on interface %s", dev->path, interface);
|
||||
|
||||
_device_parse_properties(dev, changed);
|
||||
|
||||
/* Update global state from device */
|
||||
extern void iwd_state_update_from_device(IWD_Device *dev);
|
||||
iwd_state_update_from_device(dev);
|
||||
}
|
||||
|
||||
/* Parse device properties */
|
||||
static void
|
||||
_on_sta_props_changed(void *data, const Eldbus_Message *msg)
|
||||
_device_parse_properties(IWD_Device *dev,
|
||||
Eldbus_Message_Iter *properties)
|
||||
{
|
||||
Iwd_Device *d = data;
|
||||
const char *iface;
|
||||
Eldbus_Message_Iter *changed, *invalidated;
|
||||
if (!eldbus_message_arguments_get(msg, "sa{sv}as", &iface, &changed, &invalidated))
|
||||
return;
|
||||
if (strcmp(iface, IWD_IFACE_STATION) != 0) return;
|
||||
iwd_props_for_each(changed, _sta_prop_cb, d);
|
||||
if (d->manager) iwd_manager_notify(d->manager);
|
||||
}
|
||||
|
||||
Iwd_Device *
|
||||
iwd_device_new(Eldbus_Connection *conn, const char *path, void *manager)
|
||||
{
|
||||
Iwd_Device *d = calloc(1, sizeof(*d));
|
||||
if (!d) return NULL;
|
||||
d->path = path ? strdup(path) : NULL;
|
||||
d->manager = manager;
|
||||
d->obj = eldbus_object_get(conn, IWD_BUS_NAME, path);
|
||||
if (d->obj)
|
||||
{
|
||||
d->device_proxy = eldbus_proxy_get(d->obj, IWD_IFACE_DEVICE);
|
||||
if (d->device_proxy)
|
||||
d->sh_dev_props = eldbus_proxy_properties_changed_callback_add(
|
||||
d->device_proxy, _on_dev_props_changed, d);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
void
|
||||
iwd_device_attach_station(Iwd_Device *d)
|
||||
{
|
||||
if (!d || d->station_proxy || !d->obj) return;
|
||||
d->station_proxy = eldbus_proxy_get(d->obj, IWD_IFACE_STATION);
|
||||
if (d->station_proxy)
|
||||
{
|
||||
d->has_station = EINA_TRUE;
|
||||
d->sh_sta_props = eldbus_proxy_properties_changed_callback_add(
|
||||
d->station_proxy, _on_sta_props_changed, d);
|
||||
_refresh_signals(d);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
iwd_device_detach_station(Iwd_Device *d)
|
||||
{
|
||||
if (!d) return;
|
||||
if (d->sh_sta_props) { eldbus_signal_handler_del(d->sh_sta_props); d->sh_sta_props = NULL; }
|
||||
if (d->station_proxy) { eldbus_proxy_unref(d->station_proxy); d->station_proxy = NULL; }
|
||||
d->has_station = EINA_FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
iwd_device_free(Iwd_Device *d)
|
||||
{
|
||||
if (!d) return;
|
||||
iwd_device_detach_station(d);
|
||||
if (d->sh_dev_props) eldbus_signal_handler_del(d->sh_dev_props);
|
||||
if (d->device_proxy) eldbus_proxy_unref(d->device_proxy);
|
||||
if (d->obj) eldbus_object_unref(d->obj);
|
||||
free(d->path);
|
||||
free(d->name);
|
||||
free(d->address);
|
||||
free(d->adapter_path);
|
||||
free(d->connected_network);
|
||||
free(d);
|
||||
}
|
||||
|
||||
/* Reply to Station.GetOrderedNetworks: a(on) — list of (object_path, RSSI).
|
||||
* RSSI is a 16-bit signed value in 100*dBm units. */
|
||||
static void
|
||||
_on_ordered_networks(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
|
||||
{
|
||||
Iwd_Device *d = data;
|
||||
const char *en, *em;
|
||||
if (eldbus_message_error_get(msg, &en, &em)) return;
|
||||
|
||||
Eldbus_Message_Iter *array = NULL;
|
||||
if (!eldbus_message_arguments_get(msg, "a(on)", &array) || !array)
|
||||
return;
|
||||
|
||||
const Eina_Hash *nets = d->manager ? iwd_manager_networks(d->manager) : NULL;
|
||||
Eldbus_Message_Iter *entry;
|
||||
Eina_Bool any = EINA_FALSE;
|
||||
while (eldbus_message_iter_get_and_next(array, 'r', &entry))
|
||||
{
|
||||
const char *path = NULL;
|
||||
int16_t rssi = 0;
|
||||
if (!eldbus_message_iter_arguments_get(entry, "on", &path, &rssi)) continue;
|
||||
if (!nets || !path) continue;
|
||||
Iwd_Network *n = eina_hash_find(nets, path);
|
||||
if (!n) continue;
|
||||
n->signal_dbm = rssi;
|
||||
n->have_signal = EINA_TRUE;
|
||||
any = EINA_TRUE;
|
||||
}
|
||||
if (any && d->manager) iwd_manager_notify(d->manager);
|
||||
}
|
||||
|
||||
static void
|
||||
_refresh_signals(Iwd_Device *d)
|
||||
{
|
||||
if (!d || !d->station_proxy) return;
|
||||
eldbus_proxy_call(d->station_proxy, "GetOrderedNetworks",
|
||||
_on_ordered_networks, d, -1, "");
|
||||
}
|
||||
if (!properties) return;
|
||||
|
||||
void
|
||||
iwd_device_scan(Iwd_Device *d)
|
||||
{
|
||||
if (!d || !d->station_proxy) return;
|
||||
eldbus_proxy_call(d->station_proxy, "Scan", NULL, NULL, -1, "");
|
||||
}
|
||||
while (eldbus_message_iter_get_and_next(properties, 'e', &entry))
|
||||
{
|
||||
const char *key;
|
||||
Eldbus_Message_Iter *var;
|
||||
|
||||
void
|
||||
iwd_device_disconnect(Iwd_Device *d)
|
||||
{
|
||||
if (!d || !d->station_proxy) return;
|
||||
eldbus_proxy_call(d->station_proxy, "Disconnect", NULL, NULL, -1, "");
|
||||
}
|
||||
if (!eldbus_message_iter_arguments_get(entry, "sv", &key, &var))
|
||||
continue;
|
||||
|
||||
static void
|
||||
_on_connect_hidden_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
|
||||
{
|
||||
const char *en, *em;
|
||||
char *ssid = data;
|
||||
if (eldbus_message_error_get(msg, &en, &em))
|
||||
fprintf(stderr, "e_iwd: ConnectHiddenNetwork('%s') failed: %s: %s\n",
|
||||
ssid ? ssid : "?", en, em);
|
||||
free(ssid);
|
||||
}
|
||||
|
||||
void
|
||||
iwd_device_connect_hidden(Iwd_Device *d, const char *ssid)
|
||||
{
|
||||
if (!d || !d->station_proxy || !ssid || !*ssid) return;
|
||||
eldbus_proxy_call(d->station_proxy, "ConnectHiddenNetwork",
|
||||
_on_connect_hidden_reply, strdup(ssid), -1, "s", ssid);
|
||||
if (strcmp(key, "Name") == 0)
|
||||
{
|
||||
const char *name;
|
||||
if (eldbus_message_iter_arguments_get(var, "s", &name))
|
||||
{
|
||||
eina_stringshare_replace(&dev->name, name);
|
||||
DBG(" Name: %s", dev->name);
|
||||
}
|
||||
}
|
||||
else if (strcmp(key, "Address") == 0)
|
||||
{
|
||||
const char *address;
|
||||
if (eldbus_message_iter_arguments_get(var, "s", &address))
|
||||
{
|
||||
eina_stringshare_replace(&dev->address, address);
|
||||
DBG(" Address: %s", dev->address);
|
||||
}
|
||||
}
|
||||
else if (strcmp(key, "Powered") == 0)
|
||||
{
|
||||
Eina_Bool powered;
|
||||
if (eldbus_message_iter_arguments_get(var, "b", &powered))
|
||||
{
|
||||
dev->powered = powered;
|
||||
DBG(" Powered: %d", dev->powered);
|
||||
}
|
||||
}
|
||||
else if (strcmp(key, "Scanning") == 0)
|
||||
{
|
||||
Eina_Bool scanning;
|
||||
if (eldbus_message_iter_arguments_get(var, "b", &scanning))
|
||||
{
|
||||
dev->scanning = scanning;
|
||||
DBG(" Scanning: %d", dev->scanning);
|
||||
}
|
||||
}
|
||||
else if (strcmp(key, "State") == 0)
|
||||
{
|
||||
const char *state;
|
||||
if (eldbus_message_iter_arguments_get(var, "s", &state))
|
||||
{
|
||||
eina_stringshare_replace(&dev->state, state);
|
||||
DBG(" State: %s", dev->state);
|
||||
}
|
||||
}
|
||||
else if (strcmp(key, "ConnectedNetwork") == 0)
|
||||
{
|
||||
const char *network;
|
||||
if (eldbus_message_iter_arguments_get(var, "o", &network))
|
||||
{
|
||||
eina_stringshare_replace(&dev->connected_network, network);
|
||||
DBG(" Connected network: %s", dev->connected_network);
|
||||
}
|
||||
}
|
||||
else if (strcmp(key, "Mode") == 0)
|
||||
{
|
||||
const char *mode;
|
||||
if (eldbus_message_iter_arguments_get(var, "s", &mode))
|
||||
{
|
||||
eina_stringshare_replace(&dev->mode, mode);
|
||||
DBG(" Mode: %s", dev->mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,49 +4,45 @@
|
|||
#include <Eina.h>
|
||||
#include <Eldbus.h>
|
||||
|
||||
typedef struct _Iwd_Device Iwd_Device;
|
||||
|
||||
typedef enum {
|
||||
IWD_STATION_DISCONNECTED,
|
||||
IWD_STATION_CONNECTING,
|
||||
IWD_STATION_CONNECTED,
|
||||
IWD_STATION_DISCONNECTING,
|
||||
IWD_STATION_ROAMING,
|
||||
} Iwd_Station_State;
|
||||
|
||||
struct _Iwd_Device
|
||||
/* Device structure */
|
||||
typedef struct _IWD_Device
|
||||
{
|
||||
char *path;
|
||||
char *name;
|
||||
char *address;
|
||||
char *adapter_path;
|
||||
Eina_Bool powered;
|
||||
const char *path; /* D-Bus object path */
|
||||
const char *name; /* Interface name (e.g., "wlan0") */
|
||||
const char *address; /* MAC address */
|
||||
const char *adapter_path; /* Adapter object path */
|
||||
const char *mode; /* "station", "ap", "ad-hoc" */
|
||||
Eina_Bool powered; /* Device powered state */
|
||||
|
||||
/* Station-side state, valid when station_proxy != NULL */
|
||||
Eina_Bool has_station;
|
||||
Iwd_Station_State station_state;
|
||||
Eina_Bool scanning;
|
||||
char *connected_network;
|
||||
/* Station interface properties */
|
||||
Eina_Bool scanning;
|
||||
const char *state; /* "disconnected", "connecting", "connected", "disconnecting" */
|
||||
const char *connected_network; /* Connected network object path */
|
||||
|
||||
Eldbus_Object *obj;
|
||||
Eldbus_Proxy *device_proxy;
|
||||
Eldbus_Proxy *station_proxy;
|
||||
Eldbus_Signal_Handler *sh_dev_props;
|
||||
Eldbus_Signal_Handler *sh_sta_props;
|
||||
/* D-Bus objects */
|
||||
Eldbus_Proxy *device_proxy;
|
||||
Eldbus_Proxy *station_proxy;
|
||||
Eldbus_Signal_Handler *properties_changed;
|
||||
} IWD_Device;
|
||||
|
||||
void *manager; /* back-ref, opaque (Iwd_Manager *) */
|
||||
};
|
||||
/* Global device list */
|
||||
extern Eina_List *iwd_devices;
|
||||
|
||||
Iwd_Device *iwd_device_new (Eldbus_Connection *conn, const char *path, void *manager);
|
||||
void iwd_device_free(Iwd_Device *d);
|
||||
/* Device management */
|
||||
IWD_Device *iwd_device_new(const char *path);
|
||||
void iwd_device_free(IWD_Device *dev);
|
||||
IWD_Device *iwd_device_find(const char *path);
|
||||
|
||||
void iwd_device_apply_device_props (Iwd_Device *d, Eldbus_Message_Iter *props);
|
||||
void iwd_device_apply_station_props(Iwd_Device *d, Eldbus_Message_Iter *props);
|
||||
void iwd_device_attach_station (Iwd_Device *d);
|
||||
void iwd_device_detach_station (Iwd_Device *d);
|
||||
/* Device operations */
|
||||
void iwd_device_scan(IWD_Device *dev);
|
||||
void iwd_device_disconnect(IWD_Device *dev);
|
||||
void iwd_device_connect_hidden(IWD_Device *dev, const char *ssid);
|
||||
|
||||
void iwd_device_scan (Iwd_Device *d);
|
||||
void iwd_device_disconnect (Iwd_Device *d);
|
||||
void iwd_device_connect_hidden (Iwd_Device *d, const char *ssid);
|
||||
/* Get devices list */
|
||||
Eina_List *iwd_devices_get(void);
|
||||
|
||||
/* Initialize device subsystem */
|
||||
void iwd_device_init(void);
|
||||
void iwd_device_shutdown(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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_dbus.h"
|
||||
#include "iwd_props.h"
|
||||
#include "iwd_manager.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../e_mod_main.h"
|
||||
|
||||
static Iwd_Security
|
||||
_sec_from_str(const char *s)
|
||||
/* Global network list */
|
||||
Eina_List *iwd_networks = NULL;
|
||||
|
||||
/* Forward declarations */
|
||||
static void _network_properties_changed_cb(void *data, const Eldbus_Message *msg);
|
||||
static void _network_parse_properties(IWD_Network *net, Eldbus_Message_Iter *properties);
|
||||
|
||||
/* Create new network */
|
||||
IWD_Network *
|
||||
iwd_network_new(const char *path)
|
||||
{
|
||||
if (!s) return IWD_SEC_UNKNOWN;
|
||||
if (!strcmp(s, "open")) return IWD_SEC_OPEN;
|
||||
if (!strcmp(s, "psk")) return IWD_SEC_PSK;
|
||||
if (!strcmp(s, "8021x")) return IWD_SEC_8021X;
|
||||
if (!strcmp(s, "wep")) return IWD_SEC_WEP;
|
||||
return IWD_SEC_UNKNOWN;
|
||||
}
|
||||
|
||||
static void
|
||||
_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v)
|
||||
{
|
||||
Iwd_Network *n = data;
|
||||
if (!strcmp(key, "Name")) { free(n->ssid); n->ssid = iwd_props_str_dup(v); }
|
||||
else if (!strcmp(key, "Type")) { char *s = iwd_props_str_dup(v); n->security = _sec_from_str(s); free(s); }
|
||||
else if (!strcmp(key, "Connected")) { n->connected = iwd_props_bool(v); }
|
||||
else if (!strcmp(key, "Device")) { free(n->device_path); n->device_path = iwd_props_str_dup(v); }
|
||||
else if (!strcmp(key, "KnownNetwork")) { free(n->known_path); n->known_path = iwd_props_str_dup(v); }
|
||||
IWD_Network *net;
|
||||
Eldbus_Connection *conn;
|
||||
Eldbus_Object *obj;
|
||||
|
||||
if (!path) return NULL;
|
||||
|
||||
conn = iwd_dbus_conn_get();
|
||||
if (!conn) return NULL;
|
||||
|
||||
/* Check if network already exists */
|
||||
net = iwd_network_find(path);
|
||||
if (net)
|
||||
{
|
||||
DBG("Network already exists: %s", path);
|
||||
return net;
|
||||
}
|
||||
|
||||
DBG("Creating new network: %s", path);
|
||||
|
||||
net = E_NEW(IWD_Network, 1);
|
||||
if (!net) return NULL;
|
||||
|
||||
net->path = eina_stringshare_add(path);
|
||||
|
||||
/* Create D-Bus object */
|
||||
obj = eldbus_object_get(conn, IWD_SERVICE, path);
|
||||
if (!obj)
|
||||
{
|
||||
ERR("Failed to get D-Bus object for network");
|
||||
eina_stringshare_del(net->path);
|
||||
E_FREE(net);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get proxy */
|
||||
net->network_proxy = eldbus_proxy_get(obj, IWD_NETWORK_INTERFACE);
|
||||
|
||||
/* Subscribe to property changes */
|
||||
net->properties_changed =
|
||||
eldbus_proxy_signal_handler_add(net->network_proxy,
|
||||
"PropertiesChanged",
|
||||
_network_properties_changed_cb,
|
||||
net);
|
||||
|
||||
/* Add to global list */
|
||||
iwd_networks = eina_list_append(iwd_networks, net);
|
||||
|
||||
INF("Created network: %s", path);
|
||||
|
||||
eldbus_object_unref(obj);
|
||||
return net;
|
||||
}
|
||||
|
||||
/* Free network */
|
||||
void
|
||||
iwd_network_apply_props(Iwd_Network *n, Eldbus_Message_Iter *props)
|
||||
iwd_network_free(IWD_Network *net)
|
||||
{
|
||||
iwd_props_for_each(props, _prop_cb, n);
|
||||
if (!net) return;
|
||||
|
||||
DBG("Freeing network: %s", net->path);
|
||||
|
||||
iwd_networks = eina_list_remove(iwd_networks, net);
|
||||
|
||||
if (net->properties_changed)
|
||||
eldbus_signal_handler_del(net->properties_changed);
|
||||
|
||||
eina_stringshare_del(net->path);
|
||||
eina_stringshare_del(net->name);
|
||||
eina_stringshare_del(net->type);
|
||||
eina_stringshare_del(net->last_connected_time);
|
||||
|
||||
E_FREE(net);
|
||||
}
|
||||
|
||||
static void
|
||||
_on_props_changed(void *data, const Eldbus_Message *msg)
|
||||
/* Find network by path */
|
||||
IWD_Network *
|
||||
iwd_network_find(const char *path)
|
||||
{
|
||||
Iwd_Network *n = data;
|
||||
const char *iface;
|
||||
Eina_List *l;
|
||||
IWD_Network *net;
|
||||
|
||||
if (!path) return NULL;
|
||||
|
||||
EINA_LIST_FOREACH(iwd_networks, l, net)
|
||||
{
|
||||
if (net->path && strcmp(net->path, path) == 0)
|
||||
return net;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Connect error callback */
|
||||
static void
|
||||
_network_connect_error_cb(void *data EINA_UNUSED,
|
||||
const Eldbus_Message *msg,
|
||||
Eldbus_Pending *pending EINA_UNUSED)
|
||||
{
|
||||
const char *err_name, *err_msg;
|
||||
|
||||
if (eldbus_message_error_get(msg, &err_name, &err_msg))
|
||||
{
|
||||
ERR("Failed to connect: %s: %s", err_name, err_msg);
|
||||
|
||||
/* Show user-friendly error */
|
||||
if (strstr(err_name, "NotAuthorized") || strstr(err_msg, "Not authorized"))
|
||||
{
|
||||
e_util_dialog_show("Permission Denied",
|
||||
"You do not have permission to manage Wi-Fi.<br>"
|
||||
"Please configure polkit rules for iwd.");
|
||||
}
|
||||
else if (strstr(err_name, "Failed") || strstr(err_msg, "operation failed"))
|
||||
{
|
||||
e_util_dialog_show("Connection Failed",
|
||||
"Failed to connect to the network.<br>"
|
||||
"Please check your password and try again.");
|
||||
}
|
||||
else
|
||||
{
|
||||
char buf[512];
|
||||
snprintf(buf, sizeof(buf), "Connection error:<br>%s", err_msg ? err_msg : err_name);
|
||||
e_util_dialog_show("Connection Error", buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Connect to network */
|
||||
void
|
||||
iwd_network_connect(IWD_Network *net)
|
||||
{
|
||||
if (!net || !net->network_proxy)
|
||||
{
|
||||
ERR("Invalid network for connect");
|
||||
return;
|
||||
}
|
||||
|
||||
DBG("Connecting to network: %s", net->name ? net->name : net->path);
|
||||
|
||||
/* This will trigger agent RequestPassphrase if needed */
|
||||
eldbus_proxy_call(net->network_proxy, "Connect",
|
||||
_network_connect_error_cb, NULL, -1, "");
|
||||
}
|
||||
|
||||
/* Forget network */
|
||||
void
|
||||
iwd_network_forget(IWD_Network *net)
|
||||
{
|
||||
Eldbus_Proxy *known_proxy;
|
||||
|
||||
if (!net || !net->network_proxy || !net->known)
|
||||
{
|
||||
ERR("Invalid network for forget or network not known");
|
||||
return;
|
||||
}
|
||||
|
||||
DBG("Forgetting network: %s", net->name ? net->name : net->path);
|
||||
|
||||
/* Get KnownNetwork proxy (same path, different interface) */
|
||||
known_proxy = eldbus_proxy_get(eldbus_proxy_object_get(net->network_proxy),
|
||||
IWD_KNOWN_NETWORK_INTERFACE);
|
||||
if (!known_proxy)
|
||||
{
|
||||
ERR("Failed to get KnownNetwork proxy");
|
||||
return;
|
||||
}
|
||||
|
||||
eldbus_proxy_call(known_proxy, "Forget", NULL, NULL, -1, "");
|
||||
}
|
||||
|
||||
/* Get networks list */
|
||||
Eina_List *
|
||||
iwd_networks_get(void)
|
||||
{
|
||||
return iwd_networks;
|
||||
}
|
||||
|
||||
/* Initialize network subsystem */
|
||||
void
|
||||
iwd_network_init(void)
|
||||
{
|
||||
DBG("Initializing network subsystem");
|
||||
/* Networks will be populated from ObjectManager signals */
|
||||
}
|
||||
|
||||
/* Shutdown network subsystem */
|
||||
void
|
||||
iwd_network_shutdown(void)
|
||||
{
|
||||
IWD_Network *net;
|
||||
|
||||
DBG("Shutting down network subsystem");
|
||||
|
||||
EINA_LIST_FREE(iwd_networks, net)
|
||||
iwd_network_free(net);
|
||||
}
|
||||
|
||||
/* Properties changed callback */
|
||||
static void
|
||||
_network_properties_changed_cb(void *data,
|
||||
const Eldbus_Message *msg)
|
||||
{
|
||||
IWD_Network *net = data;
|
||||
const char *interface;
|
||||
Eldbus_Message_Iter *changed, *invalidated;
|
||||
if (!eldbus_message_arguments_get(msg, "sa{sv}as", &iface, &changed, &invalidated))
|
||||
return;
|
||||
if (strcmp(iface, IWD_IFACE_NETWORK) != 0) return;
|
||||
iwd_props_for_each(changed, _prop_cb, n);
|
||||
if (n->manager) iwd_manager_notify(n->manager);
|
||||
}
|
||||
|
||||
Iwd_Network *
|
||||
iwd_network_new(Eldbus_Connection *conn, const char *path, void *manager)
|
||||
{
|
||||
Iwd_Network *n = calloc(1, sizeof(*n));
|
||||
if (!n) return NULL;
|
||||
n->path = path ? strdup(path) : NULL;
|
||||
n->manager = manager;
|
||||
n->security = IWD_SEC_UNKNOWN;
|
||||
n->obj = eldbus_object_get(conn, IWD_BUS_NAME, path);
|
||||
if (n->obj)
|
||||
{
|
||||
n->proxy = eldbus_proxy_get(n->obj, IWD_IFACE_NETWORK);
|
||||
if (n->proxy)
|
||||
n->sh_props = eldbus_proxy_properties_changed_callback_add(
|
||||
n->proxy, _on_props_changed, n);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
iwd_network_free(Iwd_Network *n)
|
||||
{
|
||||
if (!n) return;
|
||||
if (n->sh_props) eldbus_signal_handler_del(n->sh_props);
|
||||
if (n->proxy) eldbus_proxy_unref(n->proxy);
|
||||
if (n->obj) eldbus_object_unref(n->obj);
|
||||
free(n->path);
|
||||
free(n->ssid);
|
||||
free(n->device_path);
|
||||
free(n->known_path);
|
||||
free(n);
|
||||
|
||||
if (!eldbus_message_arguments_get(msg, "sa{sv}as", &interface, &changed, &invalidated))
|
||||
{
|
||||
ERR("Failed to parse PropertiesChanged signal");
|
||||
return;
|
||||
}
|
||||
|
||||
DBG("Properties changed for network %s on interface %s", net->path, interface);
|
||||
|
||||
_network_parse_properties(net, changed);
|
||||
|
||||
/* TODO: Notify UI of changes */
|
||||
}
|
||||
|
||||
/* Parse network properties */
|
||||
static void
|
||||
_on_connect_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
|
||||
_network_parse_properties(IWD_Network *net,
|
||||
Eldbus_Message_Iter *properties)
|
||||
{
|
||||
const char *en, *em;
|
||||
const char *ssid = data;
|
||||
if (eldbus_message_error_get(msg, &en, &em))
|
||||
fprintf(stderr, "e_iwd: connect to '%s' failed: %s: %s\n",
|
||||
ssid ? ssid : "?", en, em);
|
||||
free(data);
|
||||
}
|
||||
Eldbus_Message_Iter *entry;
|
||||
|
||||
int
|
||||
iwd_network_signal_tier(const Iwd_Network *n)
|
||||
{
|
||||
if (!n || !n->have_signal) return 0;
|
||||
/* iwd reports signal in 100*dBm. Cutoffs in dBm: -60/-67/-74/-80. */
|
||||
int dbm = n->signal_dbm / 100;
|
||||
if (dbm >= -60) return 4;
|
||||
if (dbm >= -67) return 3;
|
||||
if (dbm >= -74) return 2;
|
||||
if (dbm >= -80) return 1;
|
||||
return 1;
|
||||
}
|
||||
if (!properties) return;
|
||||
|
||||
void
|
||||
iwd_network_connect(Iwd_Network *n)
|
||||
{
|
||||
if (!n || !n->proxy) return;
|
||||
/* Network.Connect() takes no args; iwd will dial the registered Agent
|
||||
* for a passphrase if needed. */
|
||||
eldbus_proxy_call(n->proxy, "Connect", _on_connect_reply,
|
||||
n->ssid ? strdup(n->ssid) : NULL, -1, "");
|
||||
}
|
||||
while (eldbus_message_iter_get_and_next(properties, 'e', &entry))
|
||||
{
|
||||
const char *key;
|
||||
Eldbus_Message_Iter *var;
|
||||
|
||||
void
|
||||
iwd_network_forget(Iwd_Network *n)
|
||||
{
|
||||
if (!n || !n->known_path || !n->obj) return;
|
||||
Eldbus_Connection *conn = eldbus_object_connection_get(n->obj);
|
||||
Eldbus_Object *kobj = eldbus_object_get(conn, IWD_BUS_NAME, n->known_path);
|
||||
if (!kobj) return;
|
||||
Eldbus_Proxy *kp = eldbus_proxy_get(kobj, IWD_IFACE_KNOWN_NETWORK);
|
||||
if (kp)
|
||||
{
|
||||
eldbus_proxy_call(kp, "Forget", NULL, NULL, -1, "");
|
||||
eldbus_proxy_unref(kp);
|
||||
}
|
||||
eldbus_object_unref(kobj);
|
||||
if (!eldbus_message_iter_arguments_get(entry, "sv", &key, &var))
|
||||
continue;
|
||||
|
||||
if (strcmp(key, "Name") == 0)
|
||||
{
|
||||
const char *name;
|
||||
if (eldbus_message_iter_arguments_get(var, "s", &name))
|
||||
{
|
||||
eina_stringshare_replace(&net->name, name);
|
||||
DBG(" Name: %s", net->name);
|
||||
}
|
||||
}
|
||||
else if (strcmp(key, "Type") == 0)
|
||||
{
|
||||
const char *type;
|
||||
if (eldbus_message_iter_arguments_get(var, "s", &type))
|
||||
{
|
||||
eina_stringshare_replace(&net->type, type);
|
||||
DBG(" Type: %s", net->type);
|
||||
}
|
||||
}
|
||||
else if (strcmp(key, "Known") == 0)
|
||||
{
|
||||
Eina_Bool known;
|
||||
if (eldbus_message_iter_arguments_get(var, "b", &known))
|
||||
{
|
||||
net->known = known;
|
||||
DBG(" Known: %d", net->known);
|
||||
}
|
||||
}
|
||||
else if (strcmp(key, "AutoConnect") == 0)
|
||||
{
|
||||
Eina_Bool auto_connect;
|
||||
if (eldbus_message_iter_arguments_get(var, "b", &auto_connect))
|
||||
{
|
||||
net->auto_connect = auto_connect;
|
||||
DBG(" Auto-connect: %d", net->auto_connect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,44 @@
|
|||
#ifndef IWD_NETWORK_H
|
||||
#define IWD_NETWORK_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <Eina.h>
|
||||
#include <Eldbus.h>
|
||||
|
||||
typedef enum {
|
||||
IWD_SEC_OPEN,
|
||||
IWD_SEC_PSK,
|
||||
IWD_SEC_8021X,
|
||||
IWD_SEC_WEP,
|
||||
IWD_SEC_UNKNOWN,
|
||||
} Iwd_Security;
|
||||
|
||||
typedef struct _Iwd_Network Iwd_Network;
|
||||
|
||||
struct _Iwd_Network
|
||||
/* Network structure */
|
||||
typedef struct _IWD_Network
|
||||
{
|
||||
char *path;
|
||||
char *ssid;
|
||||
char *device_path;
|
||||
char *known_path;
|
||||
Iwd_Security security;
|
||||
Eina_Bool connected;
|
||||
const char *path; /* D-Bus object path */
|
||||
const char *name; /* SSID (decoded) */
|
||||
const char *type; /* "open", "psk", "8021x" */
|
||||
Eina_Bool known; /* Is this a known network? */
|
||||
int16_t signal_strength; /* Signal strength in dBm */
|
||||
|
||||
/* Signal strength in 100*dBm units (iwd convention). Valid iff have_signal. */
|
||||
int16_t signal_dbm;
|
||||
Eina_Bool have_signal;
|
||||
/* Known network properties */
|
||||
Eina_Bool auto_connect;
|
||||
const char *last_connected_time;
|
||||
|
||||
Eldbus_Object *obj;
|
||||
Eldbus_Proxy *proxy;
|
||||
Eldbus_Signal_Handler *sh_props;
|
||||
/* D-Bus objects */
|
||||
Eldbus_Proxy *network_proxy;
|
||||
Eldbus_Signal_Handler *properties_changed;
|
||||
} IWD_Network;
|
||||
|
||||
void *manager;
|
||||
};
|
||||
/* Global network list */
|
||||
extern Eina_List *iwd_networks;
|
||||
|
||||
Iwd_Network *iwd_network_new (Eldbus_Connection *conn, const char *path, void *manager);
|
||||
void iwd_network_free(Iwd_Network *n);
|
||||
/* Network management */
|
||||
IWD_Network *iwd_network_new(const char *path);
|
||||
void iwd_network_free(IWD_Network *net);
|
||||
IWD_Network *iwd_network_find(const char *path);
|
||||
|
||||
void iwd_network_apply_props(Iwd_Network *n, Eldbus_Message_Iter *props);
|
||||
/* Network operations */
|
||||
void iwd_network_connect(IWD_Network *net);
|
||||
void iwd_network_forget(IWD_Network *net);
|
||||
|
||||
/* 0 = unknown/no signal, 1..4 = weak..excellent. */
|
||||
int iwd_network_signal_tier(const Iwd_Network *n);
|
||||
/* Get networks list */
|
||||
Eina_List *iwd_networks_get(void);
|
||||
|
||||
void iwd_network_connect (Iwd_Network *n);
|
||||
/* Forget acts on the KnownNetwork object referenced by this network. */
|
||||
void iwd_network_forget (Iwd_Network *n);
|
||||
/* Initialize network subsystem */
|
||||
void iwd_network_init(void);
|
||||
void iwd_network_shutdown(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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_config.c',
|
||||
'e_mod_gadget.c',
|
||||
'e_mod_popup.c',
|
||||
'iwd/iwd_dbus.c',
|
||||
'iwd/iwd_adapter.c',
|
||||
'iwd/iwd_props.c',
|
||||
'iwd/iwd_agent.c',
|
||||
'iwd/iwd_manager.c',
|
||||
'iwd/iwd_device.c',
|
||||
'iwd/iwd_network.c',
|
||||
'ui/wifi_list.c',
|
||||
'iwd/iwd_agent.c',
|
||||
'iwd/iwd_state.c',
|
||||
'ui/wifi_auth.c',
|
||||
'ui/wifi_hidden.c',
|
||||
'ui/wifi_status.c',
|
||||
)
|
||||
|
||||
# All core functionality implemented
|
||||
|
||||
module_deps = [
|
||||
enlightenment,
|
||||
eldbus,
|
||||
elementary,
|
||||
ecore,
|
||||
evas,
|
||||
edje,
|
||||
eina
|
||||
]
|
||||
|
||||
module_includes = include_directories('.', 'iwd', 'ui')
|
||||
|
||||
shared_module('module',
|
||||
e_iwd_sources,
|
||||
name_prefix : '',
|
||||
name_suffix : 'so',
|
||||
dependencies : [eldbus, elementary, enlightenment],
|
||||
include_directories : include_directories('.', 'iwd', 'ui'),
|
||||
install : true,
|
||||
install_dir : join_paths(module_dir, module_arch),
|
||||
module_sources,
|
||||
dependencies: module_deps,
|
||||
include_directories: module_includes,
|
||||
name_prefix: '',
|
||||
install: true,
|
||||
install_dir: dir_module_arch
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,115 +1,171 @@
|
|||
#include "wifi_auth.h"
|
||||
#include <stdlib.h>
|
||||
#include "../e_mod_main.h"
|
||||
|
||||
typedef struct _Auth_Ctx
|
||||
/* Auth dialog structure */
|
||||
typedef struct _Auth_Dialog
|
||||
{
|
||||
Evas_Object *win; /* top-level window hosting the popup */
|
||||
Evas_Object *popup;
|
||||
Instance *inst;
|
||||
IWD_Network *network;
|
||||
E_Dialog *dialog;
|
||||
Evas_Object *entry;
|
||||
Wifi_Auth_Cb cb;
|
||||
void *data;
|
||||
Eina_Bool fired;
|
||||
} Auth_Ctx;
|
||||
char *passphrase;
|
||||
} Auth_Dialog;
|
||||
|
||||
/* Global auth dialog (only one at a time) */
|
||||
static Auth_Dialog *auth_dialog = NULL;
|
||||
|
||||
/* Forward declarations */
|
||||
static void _auth_dialog_ok_cb(void *data, E_Dialog *dialog);
|
||||
static void _auth_dialog_cancel_cb(void *data, E_Dialog *dialog);
|
||||
static void _auth_dialog_free(Auth_Dialog *ad);
|
||||
|
||||
/* Show authentication dialog */
|
||||
void
|
||||
wifi_auth_dialog_show(Instance *inst, IWD_Network *net)
|
||||
{
|
||||
Auth_Dialog *ad;
|
||||
E_Dialog *dia;
|
||||
Evas_Object *o, *entry;
|
||||
char buf[512];
|
||||
|
||||
if (!inst || !net) return;
|
||||
|
||||
/* Only one auth dialog at a time */
|
||||
if (auth_dialog)
|
||||
{
|
||||
WRN("Auth dialog already open");
|
||||
return;
|
||||
}
|
||||
|
||||
DBG("Showing auth dialog for network: %s", net->name ? net->name : net->path);
|
||||
|
||||
ad = E_NEW(Auth_Dialog, 1);
|
||||
if (!ad) return;
|
||||
|
||||
ad->inst = inst;
|
||||
ad->network = net;
|
||||
auth_dialog = ad;
|
||||
|
||||
/* Create dialog */
|
||||
dia = e_dialog_new(NULL, "E", "iwd_passphrase");
|
||||
if (!dia)
|
||||
{
|
||||
_auth_dialog_free(ad);
|
||||
return;
|
||||
}
|
||||
|
||||
ad->dialog = dia;
|
||||
|
||||
e_dialog_title_set(dia, "Wi-Fi Authentication");
|
||||
e_dialog_icon_set(dia, "network-wireless", 48);
|
||||
|
||||
/* Message */
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Enter passphrase for network:<br>"
|
||||
"<b>%s</b><br><br>"
|
||||
"Security: %s",
|
||||
net->name ? net->name : "Unknown",
|
||||
net->type ? (strcmp(net->type, "psk") == 0 ? "WPA2/WPA3" : net->type) : "Unknown");
|
||||
|
||||
o = e_widget_label_add(evas_object_evas_get(dia->win), buf);
|
||||
e_widget_size_min_set(o, 300, 40);
|
||||
|
||||
/* Entry for passphrase */
|
||||
entry = e_widget_entry_add(evas_object_evas_get(dia->win), &ad->passphrase, NULL, NULL, NULL);
|
||||
e_widget_entry_password_set(entry, 1);
|
||||
e_widget_size_min_set(entry, 280, 30);
|
||||
|
||||
/* Pack into a list */
|
||||
Evas_Object *list = e_widget_list_add(evas_object_evas_get(dia->win), 0, 0);
|
||||
e_widget_list_object_append(list, o, 1, 1, 0.5);
|
||||
e_widget_list_object_append(list, entry, 1, 1, 0.5);
|
||||
|
||||
e_dialog_content_set(dia, list, 300, 120);
|
||||
ad->entry = entry;
|
||||
|
||||
/* Buttons */
|
||||
e_dialog_button_add(dia, "Connect", NULL, _auth_dialog_ok_cb, ad);
|
||||
e_dialog_button_add(dia, "Cancel", NULL, _auth_dialog_cancel_cb, ad);
|
||||
|
||||
e_dialog_button_focus_num(dia, 0);
|
||||
e_dialog_show(dia);
|
||||
|
||||
INF("Auth dialog shown");
|
||||
}
|
||||
|
||||
/* OK button callback */
|
||||
static void
|
||||
_finish(Auth_Ctx *c, Eina_Bool ok, const char *pass)
|
||||
_auth_dialog_ok_cb(void *data, E_Dialog *dialog EINA_UNUSED)
|
||||
{
|
||||
if (c->fired) return;
|
||||
c->fired = EINA_TRUE;
|
||||
if (c->cb) c->cb(c->data, pass, ok);
|
||||
if (c->win) evas_object_del(c->win);
|
||||
free(c);
|
||||
Auth_Dialog *ad = data;
|
||||
|
||||
if (!ad) return;
|
||||
|
||||
DBG("Auth dialog OK clicked");
|
||||
|
||||
if (!ad->passphrase || strlen(ad->passphrase) < 8)
|
||||
{
|
||||
e_util_dialog_show("Error",
|
||||
"Passphrase must be at least 8 characters long.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Store passphrase in agent */
|
||||
iwd_agent_set_passphrase(ad->passphrase);
|
||||
|
||||
/* Initiate connection */
|
||||
if (ad->network)
|
||||
{
|
||||
INF("Connecting to network: %s", ad->network->name);
|
||||
iwd_network_connect(ad->network);
|
||||
}
|
||||
|
||||
/* Close dialog */
|
||||
_auth_dialog_free(ad);
|
||||
}
|
||||
|
||||
/* Cancel button callback */
|
||||
static void
|
||||
_on_ok(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
||||
_auth_dialog_cancel_cb(void *data, E_Dialog *dialog EINA_UNUSED)
|
||||
{
|
||||
Auth_Ctx *c = data;
|
||||
_finish(c, EINA_TRUE, elm_entry_entry_get(c->entry));
|
||||
Auth_Dialog *ad = data;
|
||||
|
||||
DBG("Auth dialog cancelled");
|
||||
|
||||
/* Cancel agent request */
|
||||
iwd_agent_cancel();
|
||||
|
||||
_auth_dialog_free(ad);
|
||||
}
|
||||
|
||||
/* Free auth dialog */
|
||||
static void
|
||||
_on_cancel(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
||||
_auth_dialog_free(Auth_Dialog *ad)
|
||||
{
|
||||
_finish(data, EINA_FALSE, NULL);
|
||||
if (!ad) return;
|
||||
|
||||
DBG("Freeing auth dialog");
|
||||
|
||||
if (ad->dialog)
|
||||
e_object_del(E_OBJECT(ad->dialog));
|
||||
|
||||
/* Clear passphrase from memory */
|
||||
if (ad->passphrase)
|
||||
{
|
||||
memset(ad->passphrase, 0, strlen(ad->passphrase));
|
||||
E_FREE(ad->passphrase);
|
||||
}
|
||||
|
||||
E_FREE(ad);
|
||||
auth_dialog = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
_on_del(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
||||
/* Cancel any open auth dialog */
|
||||
void
|
||||
wifi_auth_dialog_cancel(void)
|
||||
{
|
||||
/* Window closed without cancel/ok — treat as cancel. */
|
||||
_finish(data, EINA_FALSE, NULL);
|
||||
}
|
||||
|
||||
Evas_Object *
|
||||
wifi_auth_prompt(Evas_Object *parent EINA_UNUSED, const char *ssid,
|
||||
const char *security,
|
||||
Wifi_Auth_Cb cb, void *data)
|
||||
{
|
||||
Auth_Ctx *c = calloc(1, sizeof(*c));
|
||||
c->cb = cb; c->data = data;
|
||||
|
||||
/* A floating top-level window so the popup is actually visible —
|
||||
* elm_popup parented to a gadcon popup's sub-canvas never shows. */
|
||||
Evas_Object *win = elm_win_add(NULL, "eiwd-auth", ELM_WIN_DIALOG_BASIC);
|
||||
elm_win_title_set(win, "iwd Wi-Fi");
|
||||
elm_win_autodel_set(win, EINA_TRUE);
|
||||
elm_win_center(win, EINA_TRUE, EINA_TRUE);
|
||||
evas_object_resize(win, 360, 200);
|
||||
c->win = win;
|
||||
|
||||
Evas_Object *bg = elm_bg_add(win);
|
||||
evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
|
||||
elm_win_resize_object_add(win, bg);
|
||||
evas_object_show(bg);
|
||||
|
||||
Evas_Object *p = elm_popup_add(win);
|
||||
c->popup = p;
|
||||
char title[256];
|
||||
snprintf(title, sizeof(title), "Connect to %s", ssid ? ssid : "network");
|
||||
elm_object_part_text_set(p, "title,text", title);
|
||||
|
||||
Evas_Object *box = elm_box_add(p);
|
||||
elm_box_padding_set(box, 0, 6);
|
||||
|
||||
if (security && *security)
|
||||
{
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "Security: %s", security);
|
||||
Evas_Object *lbl = elm_label_add(box);
|
||||
elm_object_text_set(lbl, buf);
|
||||
evas_object_size_hint_align_set(lbl, 0.0, 0.5);
|
||||
elm_box_pack_end(box, lbl);
|
||||
evas_object_show(lbl);
|
||||
}
|
||||
|
||||
Evas_Object *entry = elm_entry_add(box);
|
||||
elm_entry_single_line_set(entry, EINA_TRUE);
|
||||
elm_entry_password_set(entry, EINA_TRUE);
|
||||
elm_entry_scrollable_set(entry, EINA_TRUE);
|
||||
evas_object_size_hint_weight_set(entry, EVAS_HINT_EXPAND, 0);
|
||||
evas_object_size_hint_align_set(entry, EVAS_HINT_FILL, 0);
|
||||
elm_box_pack_end(box, entry);
|
||||
evas_object_show(entry);
|
||||
c->entry = entry;
|
||||
|
||||
evas_object_show(box);
|
||||
elm_object_content_set(p, box);
|
||||
|
||||
Evas_Object *bcancel = elm_button_add(p);
|
||||
elm_object_text_set(bcancel, "Cancel");
|
||||
elm_object_part_content_set(p, "button1", bcancel);
|
||||
evas_object_smart_callback_add(bcancel, "clicked", _on_cancel, c);
|
||||
|
||||
Evas_Object *bok = elm_button_add(p);
|
||||
elm_object_text_set(bok, "Connect");
|
||||
elm_object_part_content_set(p, "button2", bok);
|
||||
evas_object_smart_callback_add(bok, "clicked", _on_ok, c);
|
||||
|
||||
evas_object_event_callback_add(win, EVAS_CALLBACK_DEL, _on_del, c);
|
||||
|
||||
evas_object_show(p);
|
||||
evas_object_show(win);
|
||||
elm_object_focus_set(entry, EINA_TRUE);
|
||||
return win;
|
||||
if (auth_dialog)
|
||||
{
|
||||
DBG("Cancelling auth dialog from external request");
|
||||
_auth_dialog_free(auth_dialog);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
#ifndef WIFI_AUTH_H
|
||||
#define WIFI_AUTH_H
|
||||
|
||||
#include <Elementary.h>
|
||||
#include <Eina.h>
|
||||
|
||||
typedef void (*Wifi_Auth_Cb)(void *data, const char *passphrase, Eina_Bool ok);
|
||||
/* Forward declarations */
|
||||
typedef struct _Instance Instance;
|
||||
typedef struct _IWD_Network IWD_Network;
|
||||
|
||||
/* Show a modal passphrase dialog. security is an optional human label
|
||||
* (e.g. "WPA", "WEP") shown alongside the SSID; pass NULL to omit it.
|
||||
* cb is called exactly once with ok=EINA_TRUE + passphrase, or
|
||||
* ok=EINA_FALSE on cancel. The dialog destroys itself. */
|
||||
/* Returns the popup widget so the caller can dismiss it externally
|
||||
* (e.g. on Agent.Cancel from iwd). The widget self-deletes on user
|
||||
* action; treat the returned pointer as a weak reference. */
|
||||
Evas_Object *wifi_auth_prompt(Evas_Object *parent, const char *ssid,
|
||||
const char *security,
|
||||
Wifi_Auth_Cb cb, void *data);
|
||||
/* Show authentication dialog for a network */
|
||||
void wifi_auth_dialog_show(Instance *inst, IWD_Network *net);
|
||||
|
||||
/* Cancel/close authentication dialog */
|
||||
void wifi_auth_dialog_cancel(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,115 +1,190 @@
|
|||
#include "wifi_hidden.h"
|
||||
#include <stdlib.h>
|
||||
#include "../e_mod_main.h"
|
||||
|
||||
typedef struct _Hidden_Ctx
|
||||
/* Hidden network dialog structure */
|
||||
typedef struct _Hidden_Dialog
|
||||
{
|
||||
Evas_Object *win;
|
||||
Evas_Object *popup;
|
||||
Evas_Object *e_ssid;
|
||||
Evas_Object *e_pass;
|
||||
Wifi_Hidden_Cb cb;
|
||||
void *data;
|
||||
Eina_Bool fired;
|
||||
} Hidden_Ctx;
|
||||
Instance *inst;
|
||||
E_Dialog *dialog;
|
||||
Evas_Object *ssid_entry;
|
||||
Evas_Object *pass_entry;
|
||||
char *ssid;
|
||||
char *passphrase;
|
||||
Eina_Bool has_password;
|
||||
} Hidden_Dialog;
|
||||
|
||||
static void
|
||||
_finish(Hidden_Ctx *c, Eina_Bool ok)
|
||||
{
|
||||
if (c->fired) return;
|
||||
c->fired = EINA_TRUE;
|
||||
const char *ssid = ok ? elm_entry_entry_get(c->e_ssid) : NULL;
|
||||
const char *pass = ok ? elm_entry_entry_get(c->e_pass) : NULL;
|
||||
if (c->cb) c->cb(c->data, ssid, pass, ok);
|
||||
if (c->win) evas_object_del(c->win);
|
||||
free(c);
|
||||
}
|
||||
/* Global hidden dialog */
|
||||
static Hidden_Dialog *hidden_dialog = NULL;
|
||||
|
||||
static void
|
||||
_on_ok(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
||||
{
|
||||
Hidden_Ctx *c = data;
|
||||
const char *ssid = elm_entry_entry_get(c->e_ssid);
|
||||
if (!ssid || !*ssid) return; /* require non-empty SSID */
|
||||
_finish(c, EINA_TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
_on_cancel(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
||||
{
|
||||
_finish(data, EINA_FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
_on_del(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
||||
{
|
||||
_finish(data, EINA_FALSE);
|
||||
}
|
||||
|
||||
static Evas_Object *
|
||||
_labelled_entry(Evas_Object *box, const char *label_text, Eina_Bool password)
|
||||
{
|
||||
Evas_Object *lbl = elm_label_add(box);
|
||||
elm_object_text_set(lbl, label_text);
|
||||
evas_object_size_hint_align_set(lbl, 0.0, 0.5);
|
||||
elm_box_pack_end(box, lbl);
|
||||
evas_object_show(lbl);
|
||||
|
||||
Evas_Object *e = elm_entry_add(box);
|
||||
elm_entry_single_line_set(e, EINA_TRUE);
|
||||
elm_entry_scrollable_set(e, EINA_TRUE);
|
||||
if (password) elm_entry_password_set(e, EINA_TRUE);
|
||||
evas_object_size_hint_weight_set(e, EVAS_HINT_EXPAND, 0);
|
||||
evas_object_size_hint_align_set(e, EVAS_HINT_FILL, 0);
|
||||
elm_box_pack_end(box, e);
|
||||
evas_object_show(e);
|
||||
return e;
|
||||
}
|
||||
/* Forward declarations */
|
||||
static void _hidden_dialog_ok_cb(void *data, E_Dialog *dialog);
|
||||
static void _hidden_dialog_cancel_cb(void *data, E_Dialog *dialog);
|
||||
static void _hidden_dialog_free(Hidden_Dialog *hd);
|
||||
|
||||
/* Show hidden network dialog */
|
||||
void
|
||||
wifi_hidden_prompt(Evas_Object *parent EINA_UNUSED, Wifi_Hidden_Cb cb, void *data)
|
||||
wifi_hidden_dialog_show(Instance *inst)
|
||||
{
|
||||
Hidden_Ctx *c = calloc(1, sizeof(*c));
|
||||
c->cb = cb; c->data = data;
|
||||
Hidden_Dialog *hd;
|
||||
E_Dialog *dia;
|
||||
Evas_Object *o, *list, *ssid_entry, *pass_entry;
|
||||
|
||||
/* Floating top-level so the popup actually shows. */
|
||||
Evas_Object *win = elm_win_add(NULL, "eiwd-hidden", ELM_WIN_DIALOG_BASIC);
|
||||
elm_win_title_set(win, "iwd Wi-Fi");
|
||||
elm_win_autodel_set(win, EINA_TRUE);
|
||||
elm_win_center(win, EINA_TRUE, EINA_TRUE);
|
||||
evas_object_resize(win, 360, 220);
|
||||
c->win = win;
|
||||
if (!inst) return;
|
||||
|
||||
Evas_Object *bg = elm_bg_add(win);
|
||||
evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
|
||||
elm_win_resize_object_add(win, bg);
|
||||
evas_object_show(bg);
|
||||
/* Only one hidden dialog at a time */
|
||||
if (hidden_dialog)
|
||||
{
|
||||
WRN("Hidden network dialog already open");
|
||||
return;
|
||||
}
|
||||
|
||||
Evas_Object *p = elm_popup_add(win);
|
||||
c->popup = p;
|
||||
elm_object_part_text_set(p, "title,text", "Connect to hidden network");
|
||||
DBG("Showing hidden network dialog");
|
||||
|
||||
Evas_Object *box = elm_box_add(p);
|
||||
elm_box_padding_set(box, 0, 4);
|
||||
hd = E_NEW(Hidden_Dialog, 1);
|
||||
if (!hd) return;
|
||||
|
||||
c->e_ssid = _labelled_entry(box, "SSID:", EINA_FALSE);
|
||||
c->e_pass = _labelled_entry(box, "Passphrase (optional):", EINA_TRUE);
|
||||
hd->inst = inst;
|
||||
hidden_dialog = hd;
|
||||
|
||||
evas_object_show(box);
|
||||
elm_object_content_set(p, box);
|
||||
/* Create dialog */
|
||||
dia = e_dialog_new(NULL, "E", "iwd_hidden_network");
|
||||
if (!dia)
|
||||
{
|
||||
_hidden_dialog_free(hd);
|
||||
return;
|
||||
}
|
||||
|
||||
Evas_Object *bcancel = elm_button_add(p);
|
||||
elm_object_text_set(bcancel, "Cancel");
|
||||
elm_object_part_content_set(p, "button1", bcancel);
|
||||
evas_object_smart_callback_add(bcancel, "clicked", _on_cancel, c);
|
||||
hd->dialog = dia;
|
||||
|
||||
Evas_Object *bok = elm_button_add(p);
|
||||
elm_object_text_set(bok, "Connect");
|
||||
elm_object_part_content_set(p, "button2", bok);
|
||||
evas_object_smart_callback_add(bok, "clicked", _on_ok, c);
|
||||
e_dialog_title_set(dia, "Connect to Hidden Network");
|
||||
e_dialog_icon_set(dia, "network-wireless", 48);
|
||||
|
||||
evas_object_event_callback_add(win, EVAS_CALLBACK_DEL, _on_del, c);
|
||||
/* Create content list */
|
||||
list = e_widget_list_add(evas_object_evas_get(dia->win), 0, 0);
|
||||
|
||||
evas_object_show(p);
|
||||
evas_object_show(win);
|
||||
elm_object_focus_set(c->e_ssid, EINA_TRUE);
|
||||
/* SSID label and entry */
|
||||
o = e_widget_label_add(evas_object_evas_get(dia->win), "Network Name (SSID):");
|
||||
e_widget_list_object_append(list, o, 1, 1, 0.5);
|
||||
|
||||
ssid_entry = e_widget_entry_add(evas_object_evas_get(dia->win), &hd->ssid, NULL, NULL, NULL);
|
||||
e_widget_size_min_set(ssid_entry, 280, 30);
|
||||
e_widget_list_object_append(list, ssid_entry, 1, 1, 0.5);
|
||||
hd->ssid_entry = ssid_entry;
|
||||
|
||||
/* Spacing */
|
||||
o = e_widget_label_add(evas_object_evas_get(dia->win), " ");
|
||||
e_widget_list_object_append(list, o, 1, 1, 0.5);
|
||||
|
||||
/* Passphrase label and entry */
|
||||
o = e_widget_label_add(evas_object_evas_get(dia->win), "Passphrase (leave empty for open network):");
|
||||
e_widget_list_object_append(list, o, 1, 1, 0.5);
|
||||
|
||||
pass_entry = e_widget_entry_add(evas_object_evas_get(dia->win), &hd->passphrase, NULL, NULL, NULL);
|
||||
e_widget_entry_password_set(pass_entry, 1);
|
||||
e_widget_size_min_set(pass_entry, 280, 30);
|
||||
e_widget_list_object_append(list, pass_entry, 1, 1, 0.5);
|
||||
hd->pass_entry = pass_entry;
|
||||
|
||||
e_dialog_content_set(dia, list, 300, 180);
|
||||
|
||||
/* Buttons */
|
||||
e_dialog_button_add(dia, "Connect", NULL, _hidden_dialog_ok_cb, hd);
|
||||
e_dialog_button_add(dia, "Cancel", NULL, _hidden_dialog_cancel_cb, hd);
|
||||
|
||||
e_dialog_button_focus_num(dia, 0);
|
||||
e_dialog_show(dia);
|
||||
|
||||
INF("Hidden network dialog shown");
|
||||
}
|
||||
|
||||
/* OK button callback */
|
||||
static void
|
||||
_hidden_dialog_ok_cb(void *data, E_Dialog *dialog EINA_UNUSED)
|
||||
{
|
||||
Hidden_Dialog *hd = data;
|
||||
|
||||
if (!hd) return;
|
||||
|
||||
DBG("Hidden network dialog OK clicked");
|
||||
|
||||
if (!hd->ssid || strlen(hd->ssid) == 0)
|
||||
{
|
||||
e_util_dialog_show("Error", "Please enter a network name (SSID).");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if passphrase is provided */
|
||||
hd->has_password = (hd->passphrase && strlen(hd->passphrase) > 0);
|
||||
|
||||
if (hd->has_password && strlen(hd->passphrase) < 8)
|
||||
{
|
||||
e_util_dialog_show("Error",
|
||||
"Passphrase must be at least 8 characters long.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Store passphrase if provided */
|
||||
if (hd->has_password)
|
||||
{
|
||||
iwd_agent_set_passphrase(hd->passphrase);
|
||||
}
|
||||
|
||||
/* Connect to hidden network */
|
||||
if (hd->inst && hd->inst->device)
|
||||
{
|
||||
INF("Connecting to hidden network: %s", hd->ssid);
|
||||
iwd_device_connect_hidden(hd->inst->device, hd->ssid);
|
||||
}
|
||||
|
||||
/* Close dialog */
|
||||
_hidden_dialog_free(hd);
|
||||
}
|
||||
|
||||
/* Cancel button callback */
|
||||
static void
|
||||
_hidden_dialog_cancel_cb(void *data, E_Dialog *dialog EINA_UNUSED)
|
||||
{
|
||||
Hidden_Dialog *hd = data;
|
||||
|
||||
DBG("Hidden network dialog cancelled");
|
||||
|
||||
_hidden_dialog_free(hd);
|
||||
}
|
||||
|
||||
/* Free hidden dialog */
|
||||
static void
|
||||
_hidden_dialog_free(Hidden_Dialog *hd)
|
||||
{
|
||||
if (!hd) return;
|
||||
|
||||
DBG("Freeing hidden network dialog");
|
||||
|
||||
if (hd->dialog)
|
||||
e_object_del(E_OBJECT(hd->dialog));
|
||||
|
||||
/* Clear sensitive data from memory */
|
||||
if (hd->ssid)
|
||||
{
|
||||
memset(hd->ssid, 0, strlen(hd->ssid));
|
||||
E_FREE(hd->ssid);
|
||||
}
|
||||
|
||||
if (hd->passphrase)
|
||||
{
|
||||
memset(hd->passphrase, 0, strlen(hd->passphrase));
|
||||
E_FREE(hd->passphrase);
|
||||
}
|
||||
|
||||
E_FREE(hd);
|
||||
hidden_dialog = NULL;
|
||||
}
|
||||
|
||||
/* Cancel any open hidden dialog */
|
||||
void
|
||||
wifi_hidden_dialog_cancel(void)
|
||||
{
|
||||
if (hidden_dialog)
|
||||
{
|
||||
DBG("Cancelling hidden network dialog from external request");
|
||||
_hidden_dialog_free(hidden_dialog);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
#ifndef WIFI_HIDDEN_H
|
||||
#define WIFI_HIDDEN_H
|
||||
|
||||
#include <Elementary.h>
|
||||
#include <Eina.h>
|
||||
|
||||
/* Called once with ok=EINA_TRUE + ssid (and optional passphrase, may be ""),
|
||||
* or ok=EINA_FALSE on cancel. The dialog destroys itself. */
|
||||
typedef void (*Wifi_Hidden_Cb)(void *data, const char *ssid,
|
||||
const char *passphrase, Eina_Bool ok);
|
||||
/* Forward declarations */
|
||||
typedef struct _Instance Instance;
|
||||
|
||||
void wifi_hidden_prompt(Evas_Object *parent, Wifi_Hidden_Cb cb, void *data);
|
||||
/* Show hidden network connection dialog */
|
||||
void wifi_hidden_dialog_show(Instance *inst);
|
||||
|
||||
/* Cancel/close hidden network dialog */
|
||||
void wifi_hidden_dialog_cancel(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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