diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..2dbb60e --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(mkdir:*)", + "Bash(ninja:*)", + "Bash(git add:*)" + ] + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5c75391 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,333 @@ +PRD — Enlightenment Wi-Fi Module (iwd Backend) +1. Overview +1.1 Purpose + +Create an Enlightenment module that manages Wi-Fi connections using iwd (Intel Wireless Daemon) as the backend, providing functionality similar to econnman, but without ConnMan. + +The module should: + + Integrate cleanly with Enlightenment (E17+) + + Use iwd’s D-Bus API directly + + Provide a simple, fast, and reliable Wi-Fi UI + + Follow Enlightenment UX conventions (gadget + popup) + +1.2 Motivation + + ConnMan is increasingly deprecated or undesired on many systems + + iwd is lightweight, fast, and widely adopted (Arch, Fedora, Debian) + + Enlightenment currently lacks a first-class iwd-based Wi-Fi module + + Users want a native, non-NM, non-ConnMan Wi-Fi solution + +2. Goals & Non-Goals +2.1 Goals + + Feature parity with basic econnman Wi-Fi features + + Zero dependency on NetworkManager or ConnMan + + D-Bus only (no shelling out to iwctl) + + Minimal background CPU/memory usage + + Robust behavior across suspend/resume and network changes + +2.2 Non-Goals + + Ethernet management + + VPN management + + Cellular (WWAN) support + + Advanced enterprise Wi-Fi UI (EAP tuning beyond basics) + +3. Target Users + + Enlightenment desktop users + + Minimalist / embedded systems using iwd + + Power users avoiding NetworkManager + + Distributions shipping iwd by default + +4. User Experience +4.1 Gadget (Shelf Icon) + + Status icon: + + Disconnected + + Connecting + + Connected (signal strength tiers) + + Error + + Tooltip: + + Current SSID + + Signal strength + + Security type + +4.2 Popup UI + +Triggered by clicking the gadget. +Sections: + + Current Connection + + SSID + + Signal strength + + IP (optional) + + Disconnect button + + Available Networks + + Sorted by: + + Known networks + + Signal strength + + Icons for: + + Open + + WPA2/WPA3 + + Connect button per network + + Actions + + Rescan + + Enable / Disable Wi-Fi + +4.3 Authentication Flow + + On selecting a secured network: + + Prompt for passphrase + + Optional “remember network” + + Errors clearly reported (wrong password, auth failed, etc.) + +5. Functional Requirements +5.1 Wi-Fi Control + + Enable / disable Wi-Fi (via iwd Powered) + + Trigger scan + + List available networks + + Connect to a network + + Disconnect from current network + +5.2 Network State Monitoring + + React to: + + Connection changes + + Signal strength changes + + Device availability + + iwd daemon restart + +5.3 Known Networks + + List known (previously connected) networks + + Auto-connect indication + + Forget network + +5.4 Error Handling + + iwd not running + + No wireless device + + Permission denied (polkit) + + Authentication failure + +6. Technical Requirements +6.1 Backend + + iwd via D-Bus + + Service: net.connman.iwd + + No external command execution + +6.2 D-Bus Interfaces Used (Non-Exhaustive) + + net.connman.iwd.Adapter + + net.connman.iwd.Device + + net.connman.iwd.Network + + net.connman.iwd.Station + + net.connman.iwd.KnownNetwork + +6.3 Permissions + + Requires polkit rules for: + + Scanning + + Connecting + + Forgetting networks + + Module must gracefully degrade without permissions + +7. Architecture +7.1 Module Structure + +e_iwd/ +├── e_mod_main.c +├── e_mod_config.c +├── e_mod_gadget.c +├── e_mod_popup.c +├── iwd/ +│ ├── iwd_manager.c +│ ├── iwd_device.c +│ ├── iwd_network.c +│ └── iwd_dbus.c +└── ui/ + ├── wifi_list.c + ├── wifi_auth.c + └── wifi_status.c + +7.2 Data Flow + +iwd (D-Bus) + ↓ +iwd_dbus.c + ↓ +iwd_manager / device / network + ↓ +UI layer (EFL widgets) + ↓ +Gadget / Popup + +7.3 Threading Model + + Single main loop + + Async D-Bus calls via EFL + + No blocking calls on UI thread + +8. State Model +8.1 Connection States + + OFF + + IDLE + + SCANNING + + CONNECTING + + CONNECTED + + ERROR + +8.2 Transitions + + Triggered by: + + User actions + + iwd signals + + System suspend/resume + +9. Configuration +9.1 Module Settings + + Auto-connect enabled / disabled + + Show hidden networks + + Signal strength refresh interval + + Preferred adapter (if multiple) + +Stored using Enlightenment module config system. +10. Performance & Reliability +10.1 Performance + + Startup time < 100 ms + + No periodic polling; signal-driven updates + + Minimal memory footprint (< 5 MB) + +10.2 Reliability + + Handle iwd restart gracefully + + Auto-rebind D-Bus objects + + Avoid crashes on device hot-plug + +11. Security Considerations + + Never log passphrases + + Passphrases only sent over D-Bus to iwd + + Respect system polkit policies + + No plaintext storage in module config + + +13. Success Metrics + + Successful connect/disconnect in ≥ 99% cases + + No UI freezes during scan/connect + + Parity with econnman Wi-Fi UX + + Adoption by at least one major distro Enlightenment spin + +14. Future Extensions (Out of Scope) + + Ethernet support + + VPN integration + + QR-based Wi-Fi sharing + + Per-network advanced EAP UI + +15. Open Questions / Risks + + Polkit UX integration (password prompts) + + Multiple adapter handling UX + + iwd API changes across versions diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..cde36cb --- /dev/null +++ b/PLAN.md @@ -0,0 +1,421 @@ +# Implementation Plan: eiwd - Enlightenment Wi-Fi Module (iwd Backend) + +## Project Overview + +Create a production-ready Enlightenment module that manages Wi-Fi connections using iwd's D-Bus API, providing a gadget + popup UI following Enlightenment conventions. + +**Current State**: Fresh workspace with only CLAUDE.md PRD +**Target**: Feature parity with econnman Wi-Fi functionality using iwd instead of ConnMan + +## System Context + +- **Enlightenment**: 0.27.1 (Module API version 25) +- **iwd daemon**: Running and accessible via D-Bus (`net.connman.iwd`) +- **Build tools**: Meson, GCC, pkg-config available +- **Libraries**: EFL (eldbus, elementary, ecore, evas, edje, eina) + E headers + +## Implementation Phases + +### Phase 1: Build System & Module Skeleton + +**Goal**: Create loadable .so module with proper build infrastructure + +**Files to Create**: +- `meson.build` (root) - Project definition, dependencies, installation paths +- `src/meson.build` - Source compilation +- `data/meson.build` - Desktop file and theme compilation +- `data/module.desktop` - Module metadata +- `src/e_mod_main.c` - Module entry point (e_modapi_init/shutdown/save) +- `src/e_mod_main.h` - Module structures and config + +**Key Components**: + +1. **Meson root build**: + - Dependencies: enlightenment, eldbus, elementary, ecore, evas, edje, eina + - Installation path: `/usr/lib64/enlightenment/modules/iwd/linux-gnu-x86_64-0.27/module.so` + +2. **Module entry point** (`e_mod_main.c`): + ```c + E_API E_Module_Api e_modapi = { E_MODULE_API_VERSION, "IWD" }; + E_API void *e_modapi_init(E_Module *m); + E_API int e_modapi_shutdown(E_Module *m); + E_API int e_modapi_save(E_Module *m); + ``` + +3. **Config structure** (stored via EET): + - config_version + - auto_connect (bool) + - show_hidden_networks (bool) + - signal_refresh_interval + - preferred_adapter + +**Verification**: Module loads in Enlightenment without crashing + +--- + +### Phase 2: D-Bus Layer (iwd Backend) + +**Goal**: Establish communication with iwd daemon and abstract devices/networks + +**Files to Create**: +- `src/iwd/iwd_dbus.c` + `.h` - D-Bus connection management +- `src/iwd/iwd_device.c` + `.h` - Device abstraction (Station interface) +- `src/iwd/iwd_network.c` + `.h` - Network abstraction +- `src/iwd/iwd_agent.c` + `.h` - Agent for passphrase requests + +**Key Implementations**: + +1. **D-Bus Manager** (`iwd_dbus.c`): + - Connect to system bus `net.connman.iwd` + - Subscribe to ObjectManager signals (InterfacesAdded/Removed) + - Monitor NameOwnerChanged for iwd daemon restart + - Provide signal subscription helpers + +2. **Device Abstraction** (`iwd_device.c`): + ```c + typedef struct _IWD_Device { + char *path, *name, *address; + Eina_Bool powered, scanning; + char *state; // "disconnected", "connecting", "connected" + Eldbus_Proxy *device_proxy, *station_proxy; + } IWD_Device; + ``` + - Operations: scan, disconnect, connect_hidden, get_networks + +3. **Network Abstraction** (`iwd_network.c`): + ```c + typedef struct _IWD_Network { + char *path, *name, *type; // "open", "psk", "8021x" + Eina_Bool known; + int16_t signal_strength; // dBm + Eldbus_Proxy *network_proxy; + } IWD_Network; + ``` + - Operations: connect, forget + +4. **Agent Implementation** (`iwd_agent.c`): + - Register D-Bus service at `/org/enlightenment/eiwd/agent` + - Implement `RequestPassphrase(network_path)` method + - Bridge between iwd requests and UI dialogs + - **Security**: Never log passphrases, clear from memory after sending + +**Verification**: Can list devices, trigger scan, receive PropertyChanged signals + +--- + +### Phase 3: Gadget & Basic UI + +**Goal**: Create shelf icon and popup interface + +**Files to Create**: +- `src/e_mod_gadget.c` - Gadcon provider and icon +- `src/e_mod_popup.c` - Popup window and layout +- `src/ui/wifi_status.c` + `.h` - Current connection widget +- `src/ui/wifi_list.c` + `.h` - Network list widget + +**Key Implementations**: + +1. **Gadcon Provider** (`e_mod_gadget.c`): + ```c + static const E_Gadcon_Client_Class _gc_class = { + GADCON_CLIENT_CLASS_VERSION, "iwd", { ... } + }; + ``` + - Icon states via edje: disconnected, connecting, connected (signal tiers), error + - Click handler: toggle popup + - Tooltip: SSID, signal strength, security type + +2. **Popup Window** (`e_mod_popup.c`): + - Layout: Current Connection + Available Networks + Actions + - Current: SSID, signal, IP, disconnect button + - Networks: sorted by known → signal strength + - Actions: Rescan, Enable/Disable Wi-Fi + +3. **Network List Widget** (`ui/wifi_list.c`): + - Use elm_genlist or e_widget_ilist + - Icons: open/WPA2/WPA3 lock icons + - Sort: known networks first, then by signal + - Click handler: initiate connection + +**Verification**: Gadget appears on shelf, popup opens/closes, networks display + +--- + +### Phase 4: Connection Management + +**Goal**: Complete connection flow including authentication + +**Files to Create**: +- `src/ui/wifi_auth.c` + `.h` - Passphrase dialog +- `src/iwd/iwd_state.c` + `.h` - State machine + +**Connection Flow**: +1. User clicks network in list +2. Check security type (open vs psk vs 8021x) +3. If psk: show auth dialog (`wifi_auth_dialog_show`) +4. Call `network.Connect()` D-Bus method +5. iwd calls agent's `RequestPassphrase` +6. Return passphrase from dialog +7. Monitor `Station.State` PropertyChanged +8. Update UI: connecting → connected + +**State Machine** (`iwd_state.c`): +```c +typedef enum { + IWD_STATE_OFF, // Powered = false + IWD_STATE_IDLE, // Powered = true, disconnected + IWD_STATE_SCANNING, + IWD_STATE_CONNECTING, + IWD_STATE_CONNECTED, + IWD_STATE_ERROR // iwd not running +} IWD_State; +``` + +**Known Networks**: +- List via KnownNetwork interface +- Operations: Forget, Set AutoConnect +- UI: star icon for known, context menu + +**Verification**: Connect to open/WPA2 networks, disconnect, forget network + +--- + +### Phase 5: Advanced Features + +**Goal**: Handle edge cases and advanced scenarios + +**Implementations**: + +1. **Hidden Networks**: + - Add "Connect to Hidden Network" button + - Call `Station.ConnectHiddenNetwork(ssid)` + +2. **Multiple Adapters**: + - Monitor all `/net/connman/iwd/[0-9]+` paths + - UI: dropdown/tabs if multiple devices + - Config: preferred adapter selection + +3. **Daemon Restart Handling**: + - Monitor NameOwnerChanged for `net.connman.iwd` + - On restart: re-query ObjectManager, re-register agent, recreate proxies + - Set error state while daemon down + +4. **Polkit Integration**: + - Detect `NotAuthorized` errors + - Show user-friendly permission error dialog + - Document required polkit rules (don't auto-install) + +**Error Handling**: +- iwd not running → error state icon +- No wireless device → graceful message +- Permission denied → polkit error dialog +- Auth failure → clear error message (wrong password) + +**Verification**: Handle iwd restart, multiple adapters, polkit errors + +--- + +### Phase 6: Theme & Polish + +**Goal**: Professional UI appearance and internationalization + +**Files to Create**: +- `data/theme.edc` - Edje theme definition +- `data/icons/*.svg` - Icon source files +- `po/POTFILES.in` - i18n file list +- `po/eiwd.pot` - Translation template +- `src/e_mod_config.c` - Configuration dialog + +**Theme Groups** (`theme.edc`): +- `e/modules/iwd/main` - Gadget icon +- `e/modules/iwd/signal/{0,25,50,75,100}` - Signal strength icons + +**Configuration Dialog** (`e_mod_config.c`): +- Auto-connect to known networks: checkbox +- Show hidden networks: checkbox +- Signal refresh interval: slider (1-60s) +- Preferred adapter: dropdown + +**i18n**: +- Mark strings with `D_(str)` macro (dgettext) +- Meson gettext integration + +**Verification**: Theme scales properly, config saves, translations work + +--- + +### Phase 7: Testing & Documentation + +**Testing**: +- Unit tests: SSID parsing, signal conversion, config serialization +- Memory leak check: Valgrind during connect/disconnect cycles +- Manual checklist: + - [ ] Module loads without errors + - [ ] Scan, connect, disconnect work + - [ ] Wrong password shows error + - [ ] Known network auto-connect + - [ ] iwd restart recovery + - [ ] Suspend/resume handling + - [ ] No device graceful degradation + +**Documentation** (`README.md`, `INSTALL.md`): +- Overview and features +- Dependencies +- Building with Meson +- Installation paths +- iwd setup requirements +- Polkit configuration +- Troubleshooting + +**Verification**: All tests pass, documentation complete + +--- + +### Phase 8: Packaging & Distribution + +**Packaging**: +- Arch Linux PKGBUILD +- Gentoo ebuild +- Generic tarball + +**Installation**: +```bash +meson setup build +ninja -C build +sudo ninja -C build install +``` + +Module location: `/usr/lib64/enlightenment/modules/iwd/` + +**Verification**: Clean install works, module appears in E module list + +--- + +## Directory Structure + +``` +/home/nemunaire/workspace/eiwd/ +├── meson.build # Root build config +├── meson_options.txt +├── README.md +├── INSTALL.md +├── LICENSE +├── data/ +│ ├── meson.build +│ ├── module.desktop # Module metadata +│ ├── theme.edc # Edje theme +│ └── icons/ # SVG/PNG icons +├── po/ # i18n +│ ├── POTFILES.in +│ └── eiwd.pot +├── src/ +│ ├── meson.build +│ ├── e_mod_main.c # Module entry point +│ ├── e_mod_main.h +│ ├── e_mod_config.c # Config dialog +│ ├── e_mod_gadget.c # Shelf icon +│ ├── e_mod_popup.c # Popup window +│ ├── iwd/ +│ │ ├── iwd_dbus.c # D-Bus connection +│ │ ├── iwd_dbus.h +│ │ ├── iwd_device.c # Device abstraction +│ │ ├── iwd_device.h +│ │ ├── iwd_network.c # Network abstraction +│ │ ├── iwd_network.h +│ │ ├── iwd_agent.c # Agent implementation +│ │ ├── iwd_agent.h +│ │ ├── iwd_state.c # State machine +│ │ └── iwd_state.h +│ └── ui/ +│ ├── wifi_status.c # Connection status widget +│ ├── wifi_status.h +│ ├── wifi_list.c # Network list widget +│ ├── wifi_list.h +│ ├── wifi_auth.c # Passphrase dialog +│ └── wifi_auth.h +└── tests/ + ├── meson.build + └── test_network.c +``` + +--- + +## Critical Files (Implementation Order) + +1. **`meson.build`** - Build system foundation +2. **`src/e_mod_main.c`** - Module lifecycle (init/shutdown/save) +3. **`src/iwd/iwd_dbus.c`** - D-Bus connection to iwd +4. **`src/iwd/iwd_agent.c`** - Passphrase handling (essential for secured networks) +5. **`src/e_mod_gadget.c`** - Primary user interface (shelf icon) + +--- + +## Key Technical Decisions + +**Build System**: Meson (modern, used by newer E modules) +**UI Framework**: Elementary widgets (EFL/Enlightenment standard) +**D-Bus Library**: eldbus (EFL integration, async) +**State Management**: Signal-driven (no polling) +**Security**: Never log passphrases, rely on iwd for credential storage + +--- + +## Performance Targets + +- Startup: < 100ms +- Popup open: < 200ms +- Network scan: < 2s +- Memory footprint: < 5 MB +- No periodic polling (signal-driven only) + +--- + +## Dependencies + +**Build**: +- meson >= 0.56 +- ninja +- gcc/clang +- pkg-config +- edje_cc + +**Runtime**: +- enlightenment >= 0.25 +- efl (elementary, eldbus, ecore, evas, edje, eina) +- iwd >= 1.0 +- dbus + +**Optional**: +- polkit (permissions management) + +--- + +## Security Considerations + +1. **Never log passphrases** - No debug output of credentials +2. **Clear sensitive data** - memset passphrases after use +3. **D-Bus only** - No plaintext credential storage in module +4. **Polkit enforcement** - Respect system authorization policies +5. **Validate D-Bus params** - Don't trust all incoming data + +--- + +## Known Limitations + +- No VPN support (out of scope per PRD) +- No ethernet management (iwd is Wi-Fi only) +- Basic EAP UI (username/password only, no advanced cert config) +- No WPS support in initial version + +--- + +## Success Criteria + +- Module loads and appears in Enlightenment module list +- Can scan for networks and display them sorted by known + signal +- Can connect to open and WPA2/WPA3 networks with passphrase +- Can disconnect and forget networks +- Handles iwd daemon restart gracefully +- No UI freezes during scan/connect operations +- Memory leak free (Valgrind clean) +- Feature parity with econnman Wi-Fi functionality diff --git a/data/meson.build b/data/meson.build index 06815e1..e41d4fa 100644 --- a/data/meson.build +++ b/data/meson.build @@ -3,15 +3,16 @@ install_data('module.desktop', install_dir: dir_module ) -# TODO: Theme compilation will be added in Phase 6 -# 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, '-id', join_paths(meson.current_source_dir(), 'icons'), -# '@INPUT@', '@OUTPUT@'], -# install: true, -# install_dir: dir_module -# ) -# endif +# Compile theme +edje_cc = find_program('edje_cc', required: false) +if edje_cc.found() + custom_target('theme', + input: 'theme.edc', + output: 'e-module-iwd.edj', + command: [edje_cc, '@INPUT@', '@OUTPUT@'], + install: true, + install_dir: dir_module + ) +else + warning('edje_cc not found, theme will not be compiled') +endif diff --git a/data/theme.edc b/data/theme.edc new file mode 100644 index 0000000..f8989c5 --- /dev/null +++ b/data/theme.edc @@ -0,0 +1,188 @@ +/* IWD Module Theme */ + +collections { + /* Main gadget icon - base group */ + group { + name: "e/modules/iwd/main"; + min: 16 16; + max: 128 128; + + parts { + /* Background */ + part { + name: "bg"; + type: RECT; + description { + state: "default" 0.0; + color: 0 0 0 0; /* Transparent */ + } + } + + /* Wi-Fi icon base */ + part { + name: "icon"; + type: RECT; + description { + state: "default" 0.0; + rel1.relative: 0.1 0.1; + rel2.relative: 0.9 0.9; + color: 128 128 128 255; /* Gray - disconnected */ + } + description { + state: "connected" 0.0; + inherit: "default" 0.0; + color: 0 200 0 255; /* Green - connected */ + } + description { + state: "connecting" 0.0; + inherit: "default" 0.0; + color: 255 165 0 255; /* Orange - connecting */ + } + description { + state: "error" 0.0; + inherit: "default" 0.0; + color: 255 0 0 255; /* Red - error */ + } + } + + /* Signal strength indicator */ + part { + name: "signal"; + type: RECT; + description { + state: "default" 0.0; + visible: 0; + rel1.relative: 0.7 0.7; + rel2.relative: 0.95 0.95; + color: 255 255 255 200; + } + description { + state: "visible" 0.0; + inherit: "default" 0.0; + visible: 1; + } + } + } + + programs { + program { + name: "go_connected"; + signal: "e,state,connected"; + source: "e"; + action: STATE_SET "connected" 0.0; + target: "icon"; + } + program { + name: "go_connecting"; + signal: "e,state,connecting"; + source: "e"; + action: STATE_SET "connecting" 0.0; + target: "icon"; + } + program { + name: "go_disconnected"; + signal: "e,state,disconnected"; + source: "e"; + action: STATE_SET "default" 0.0; + target: "icon"; + } + program { + name: "go_error"; + signal: "e,state,error"; + source: "e"; + action: STATE_SET "error" 0.0; + target: "icon"; + } + program { + name: "signal_show"; + signal: "e,signal,show"; + source: "e"; + action: STATE_SET "visible" 0.0; + target: "signal"; + } + program { + name: "signal_hide"; + signal: "e,signal,hide"; + source: "e"; + action: STATE_SET "default" 0.0; + target: "signal"; + } + } + } + + /* Signal strength icons */ + group { + name: "e/modules/iwd/signal/0"; + min: 16 16; + parts { + part { + name: "base"; + type: RECT; + description { + state: "default" 0.0; + color: 64 64 64 255; + } + } + } + } + + group { + name: "e/modules/iwd/signal/25"; + min: 16 16; + parts { + part { + name: "base"; + type: RECT; + description { + state: "default" 0.0; + color: 255 64 64 255; /* Red - weak */ + } + } + } + } + + group { + name: "e/modules/iwd/signal/50"; + min: 16 16; + parts { + part { + name: "base"; + type: RECT; + description { + state: "default" 0.0; + color: 255 165 0 255; /* Orange - fair */ + } + } + } + } + + group { + name: "e/modules/iwd/signal/75"; + min: 16 16; + parts { + part { + name: "base"; + type: RECT; + description { + state: "default" 0.0; + color: 200 200 0 255; /* Yellow - good */ + } + } + } + } + + group { + name: "e/modules/iwd/signal/100"; + min: 16 16; + parts { + part { + name: "base"; + type: RECT; + description { + state: "default" 0.0; + color: 0 255 0 255; /* Green - excellent */ + } + } + } + } +} diff --git a/meson.build b/meson.build index fc2b3bd..c316a18 100644 --- a/meson.build +++ b/meson.build @@ -48,6 +48,12 @@ add_project_arguments( language: 'c' ) +# Internationalization +i18n = import('i18n') +if get_option('nls') + subdir('po') +endif + # Subdirectories subdir('src') subdir('data') diff --git a/meson_options.txt b/meson_options.txt index 0080dd5..a86193c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1 +1,3 @@ -# No custom options for now +# Build options +option('nls', type: 'boolean', value: true, + description: 'Enable internationalization support') diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..b85159b --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,10 @@ +# List of source files which contain translatable strings +src/e_mod_main.c +src/e_mod_config.c +src/e_mod_gadget.c +src/e_mod_popup.c +src/ui/wifi_auth.c +src/ui/wifi_hidden.c +src/iwd/iwd_dbus.c +src/iwd/iwd_network.c +src/iwd/iwd_device.c diff --git a/po/meson.build b/po/meson.build new file mode 100644 index 0000000..b99f819 --- /dev/null +++ b/po/meson.build @@ -0,0 +1,7 @@ +# i18n support +i18n.gettext('eiwd', + args: [ + '--directory=' + meson.source_root(), + '--from-code=UTF-8', + ] +) diff --git a/src/e_mod_config.c b/src/e_mod_config.c new file mode 100644 index 0000000..281274c --- /dev/null +++ b/src/e_mod_config.c @@ -0,0 +1,162 @@ +#include "e_mod_main.h" + +/* Configuration dialog structure */ +typedef struct _E_Config_Dialog_Data +{ + int auto_connect; + int show_hidden_networks; + int signal_refresh_interval; + char *preferred_adapter; +} E_Config_Dialog_Data; + +/* 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 * +_create_data(E_Config_Dialog *cfd EINA_UNUSED) +{ + E_Config_Dialog_Data *cfdata; + + if (!iwd_mod || !iwd_mod->conf) return NULL; + + cfdata = E_NEW(E_Config_Dialog_Data, 1); + if (!cfdata) return NULL; + + /* Copy current config */ + cfdata->auto_connect = iwd_mod->conf->auto_connect; + cfdata->show_hidden_networks = iwd_mod->conf->show_hidden_networks; + cfdata->signal_refresh_interval = iwd_mod->conf->signal_refresh_interval; + + if (iwd_mod->conf->preferred_adapter) + cfdata->preferred_adapter = strdup(iwd_mod->conf->preferred_adapter); + else + cfdata->preferred_adapter = NULL; + + return cfdata; +} + +/* Free config data */ +static void +_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata) +{ + if (!cfdata) return; + + if (cfdata->preferred_adapter) + free(cfdata->preferred_adapter); + + E_FREE(cfdata); +} + +/* Create basic UI */ +static Evas_Object * +_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); + + /* Connection settings frame */ + of = e_widget_framelist_add(evas, "Connection Settings", 0); + + ob = e_widget_check_add(evas, "Auto-connect to known networks", + &(cfdata->auto_connect)); + e_widget_framelist_object_append(of, ob); + + ob = e_widget_check_add(evas, "Show hidden networks", + &(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 (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, &(cfdata->signal_refresh_interval), 150); + e_widget_framelist_object_append(of, ob); + + e_widget_list_object_append(o, of, 1, 1, 0.5); + + /* Adapter settings frame (if multiple adapters available) */ + Eina_List *devices = iwd_devices_get(); + if (eina_list_count(devices) > 1) + { + of = e_widget_framelist_add(evas, "Adapter Selection", 0); + + ob = e_widget_label_add(evas, "Preferred wireless adapter:"); + e_widget_framelist_object_append(of, ob); + + /* TODO: Add radio list for adapter selection when multiple devices exist */ + ob = e_widget_label_add(evas, "(Auto-select)"); + e_widget_framelist_object_append(of, ob); + + e_widget_list_object_append(o, of, 1, 1, 0.5); + } + + return o; +} + +/* Apply configuration */ +static int +_basic_apply(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata) +{ + 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; +} diff --git a/src/e_mod_gadget.c b/src/e_mod_gadget.c index 2c98a07..a8c07a0 100644 --- a/src/e_mod_gadget.c +++ b/src/e_mod_gadget.c @@ -1,4 +1,5 @@ #include "e_mod_main.h" +#include /* Forward declarations */ static E_Gadcon_Client *_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style); @@ -78,8 +79,18 @@ _gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style) inst->icon = o; inst->gadget = o; - /* For now, use a simple colored rectangle until we have theme */ - evas_object_color_set(o, 100, 150, 200, 255); + /* 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)); + + 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_resize(o, 16, 16); evas_object_show(o); @@ -169,11 +180,28 @@ 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); - /* TODO: Load theme icon in Phase 6 */ - /* For now, return a simple colored box */ - evas_object_color_set(o, 100, 150, 200, 255); + + /* 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); + } + } + else + { + /* Fallback if module not initialized yet */ + evas_object_color_set(o, 100, 150, 200, 255); + } + evas_object_resize(o, 16, 16); return o; @@ -245,20 +273,56 @@ _gadget_update(Instance *inst) snprintf(buf, sizeof(buf), "IWD Wi-Fi\nNo device"); } - /* TODO: Update icon appearance based on state (Phase 6 with theme) */ - /* For now, change color based on connection state */ - if (inst->device && inst->device->state) + /* 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) { - 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 */ + 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 { - evas_object_color_set(inst->icon, 200, 100, 100, 255); /* Red - no device */ + /* 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 */ + } } } diff --git a/src/e_mod_main.c b/src/e_mod_main.c index 6122639..4dd0605 100644 --- a/src/e_mod_main.c +++ b/src/e_mod_main.c @@ -50,13 +50,15 @@ e_modapi_init(E_Module *m) e_iwd_config_init(); _iwd_config_load(); - /* Initialize D-Bus and iwd subsystems (Phase 2) */ + /* 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 */ } @@ -90,6 +92,7 @@ e_modapi_shutdown(E_Module *m EINA_UNUSED) iwd_dbus_shutdown(); iwd_network_shutdown(); iwd_device_shutdown(); + iwd_state_shutdown(); /* Free configuration */ _iwd_config_free(); diff --git a/src/e_mod_main.h b/src/e_mod_main.h index da354ce..8e180c0 100644 --- a/src/e_mod_main.h +++ b/src/e_mod_main.h @@ -74,6 +74,7 @@ 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); @@ -83,13 +84,15 @@ void e_iwd_gadget_shutdown(void); void iwd_popup_new(Instance *inst); void iwd_popup_del(Instance *inst); -/* Auth dialog functions */ +/* UI dialog functions */ #include "ui/wifi_auth.h" +#include "ui/wifi_hidden.h" /* D-Bus functions */ #include "iwd/iwd_dbus.h" #include "iwd/iwd_device.h" #include "iwd/iwd_network.h" #include "iwd/iwd_agent.h" +#include "iwd/iwd_state.h" #endif diff --git a/src/e_mod_popup.c b/src/e_mod_popup.c index a4f912d..ecd2410 100644 --- a/src/e_mod_popup.c +++ b/src/e_mod_popup.c @@ -4,6 +4,7 @@ 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); @@ -144,7 +145,12 @@ iwd_popup_new(Instance *inst) elm_box_pack_end(button_box, button); evas_object_show(button); - /* TODO: Add more buttons (enable/disable Wi-Fi, settings) */ + /* 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); @@ -240,6 +246,23 @@ _button_disconnect_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info 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) diff --git a/src/iwd/iwd_dbus.c b/src/iwd/iwd_dbus.c index 05dce39..e5166bd 100644 --- a/src/iwd/iwd_dbus.c +++ b/src/iwd/iwd_dbus.c @@ -190,15 +190,35 @@ _iwd_dbus_name_owner_changed_cb(void *data EINA_UNUSED, if (new_id && new_id[0]) { /* iwd daemon started */ - INF("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(); - /* TODO: Notify UI to show error state */ + + /* Set error state */ + extern void iwd_state_set(IWD_State state); + iwd_state_set(IWD_STATE_ERROR); + + /* Show error dialog */ + e_util_dialog_show("IWD Wi-Fi Error", + "Wi-Fi daemon (iwd) has stopped.
" + "Please restart the iwd service."); } } diff --git a/src/iwd/iwd_device.c b/src/iwd/iwd_device.c index c66c409..a3c17ee 100644 --- a/src/iwd/iwd_device.c +++ b/src/iwd/iwd_device.c @@ -201,7 +201,9 @@ _device_properties_changed_cb(void *data, _device_parse_properties(dev, changed); - /* TODO: Notify UI of state changes */ + /* Update global state from device */ + extern void iwd_state_update_from_device(IWD_Device *dev); + iwd_state_update_from_device(dev); } /* Parse device properties */ diff --git a/src/iwd/iwd_network.c b/src/iwd/iwd_network.c index 048555f..24cdab0 100644 --- a/src/iwd/iwd_network.c +++ b/src/iwd/iwd_network.c @@ -105,6 +105,40 @@ iwd_network_find(const char *path) return NULL; } +/* Connect error callback */ +static void +_network_connect_error_cb(void *data EINA_UNUSED, + const Eldbus_Message *msg, + Eldbus_Pending *pending EINA_UNUSED) +{ + const char *err_name, *err_msg; + + if (eldbus_message_error_get(msg, &err_name, &err_msg)) + { + ERR("Failed to connect: %s: %s", err_name, err_msg); + + /* Show user-friendly error */ + if (strstr(err_name, "NotAuthorized") || strstr(err_msg, "Not authorized")) + { + e_util_dialog_show("Permission Denied", + "You do not have permission to manage Wi-Fi.
" + "Please configure polkit rules for iwd."); + } + else if (strstr(err_name, "Failed") || strstr(err_msg, "operation failed")) + { + e_util_dialog_show("Connection Failed", + "Failed to connect to the network.
" + "Please check your password and try again."); + } + else + { + char buf[512]; + snprintf(buf, sizeof(buf), "Connection error:
%s", err_msg ? err_msg : err_name); + e_util_dialog_show("Connection Error", buf); + } + } +} + /* Connect to network */ void iwd_network_connect(IWD_Network *net) @@ -117,8 +151,9 @@ iwd_network_connect(IWD_Network *net) DBG("Connecting to network: %s", net->name ? net->name : net->path); - /* TODO: This will trigger agent RequestPassphrase if needed */ - eldbus_proxy_call(net->network_proxy, "Connect", NULL, NULL, -1, ""); + /* This will trigger agent RequestPassphrase if needed */ + eldbus_proxy_call(net->network_proxy, "Connect", + _network_connect_error_cb, NULL, -1, ""); } /* Forget network */ diff --git a/src/iwd/iwd_state.c b/src/iwd/iwd_state.c new file mode 100644 index 0000000..faeb43a --- /dev/null +++ b/src/iwd/iwd_state.c @@ -0,0 +1,153 @@ +#include "iwd_state.h" +#include "../e_mod_main.h" + +/* Global state */ +static IWD_State current_state = IWD_STATE_OFF; +static Eina_List *state_change_callbacks = NULL; + +/* State change callback structure */ +typedef struct _State_Callback +{ + IWD_State_Changed_Cb cb; + void *data; +} State_Callback; + +/* Initialize state subsystem */ +void +iwd_state_init(void) +{ + DBG("Initializing state subsystem"); + current_state = IWD_STATE_OFF; +} + +/* Shutdown state subsystem */ +void +iwd_state_shutdown(void) +{ + State_Callback *scb; + + DBG("Shutting down state subsystem"); + + EINA_LIST_FREE(state_change_callbacks, scb) + E_FREE(scb); +} + +/* Get current state */ +IWD_State +iwd_state_get(void) +{ + return current_state; +} + +/* Set state and notify callbacks */ +void +iwd_state_set(IWD_State state) +{ + IWD_State old_state; + Eina_List *l; + State_Callback *scb; + + if (current_state == state) return; + + old_state = current_state; + current_state = state; + + DBG("State changed: %d -> %d", old_state, state); + + /* Notify callbacks */ + EINA_LIST_FOREACH(state_change_callbacks, l, scb) + { + if (scb->cb) + scb->cb(scb->data, old_state, state); + } +} + +/* Add state change callback */ +void +iwd_state_callback_add(IWD_State_Changed_Cb cb, void *data) +{ + State_Callback *scb; + + if (!cb) return; + + scb = E_NEW(State_Callback, 1); + if (!scb) return; + + scb->cb = cb; + scb->data = data; + + state_change_callbacks = eina_list_append(state_change_callbacks, scb); +} + +/* Remove state change callback */ +void +iwd_state_callback_del(IWD_State_Changed_Cb cb, void *data) +{ + Eina_List *l, *l_next; + State_Callback *scb; + + EINA_LIST_FOREACH_SAFE(state_change_callbacks, l, l_next, scb) + { + if (scb->cb == cb && scb->data == data) + { + state_change_callbacks = eina_list_remove_list(state_change_callbacks, l); + E_FREE(scb); + return; + } + } +} + +/* Update state from device */ +void +iwd_state_update_from_device(IWD_Device *dev) +{ + if (!dev) + { + iwd_state_set(IWD_STATE_ERROR); + return; + } + + if (!dev->powered) + { + iwd_state_set(IWD_STATE_OFF); + return; + } + + if (dev->scanning) + { + iwd_state_set(IWD_STATE_SCANNING); + return; + } + + if (dev->state) + { + if (strcmp(dev->state, "connected") == 0) + iwd_state_set(IWD_STATE_CONNECTED); + else if (strcmp(dev->state, "connecting") == 0) + iwd_state_set(IWD_STATE_CONNECTING); + else if (strcmp(dev->state, "disconnecting") == 0) + iwd_state_set(IWD_STATE_IDLE); + else + iwd_state_set(IWD_STATE_IDLE); + } + else + { + iwd_state_set(IWD_STATE_IDLE); + } +} + +/* Get state name */ +const char * +iwd_state_name_get(IWD_State state) +{ + switch (state) + { + case IWD_STATE_OFF: return "OFF"; + case IWD_STATE_IDLE: return "IDLE"; + case IWD_STATE_SCANNING: return "SCANNING"; + case IWD_STATE_CONNECTING: return "CONNECTING"; + case IWD_STATE_CONNECTED: return "CONNECTED"; + case IWD_STATE_ERROR: return "ERROR"; + default: return "UNKNOWN"; + } +} diff --git a/src/iwd/iwd_state.h b/src/iwd/iwd_state.h new file mode 100644 index 0000000..67ccadc --- /dev/null +++ b/src/iwd/iwd_state.h @@ -0,0 +1,41 @@ +#ifndef IWD_STATE_H +#define IWD_STATE_H + +#include + +/* Forward declaration */ +typedef struct _IWD_Device IWD_Device; + +/* Connection states */ +typedef enum +{ + IWD_STATE_OFF, /* Powered = false */ + IWD_STATE_IDLE, /* Powered = true, disconnected */ + IWD_STATE_SCANNING, /* Scanning in progress */ + IWD_STATE_CONNECTING, /* Connecting to network */ + IWD_STATE_CONNECTED, /* Connected to network */ + IWD_STATE_ERROR /* iwd not running or error */ +} IWD_State; + +/* State change callback */ +typedef void (*IWD_State_Changed_Cb)(void *data, IWD_State old_state, IWD_State new_state); + +/* Initialize/shutdown */ +void iwd_state_init(void); +void iwd_state_shutdown(void); + +/* Get/set state */ +IWD_State iwd_state_get(void); +void iwd_state_set(IWD_State state); + +/* State callbacks */ +void iwd_state_callback_add(IWD_State_Changed_Cb cb, void *data); +void iwd_state_callback_del(IWD_State_Changed_Cb cb, void *data); + +/* Update state from device */ +void iwd_state_update_from_device(IWD_Device *dev); + +/* Get state name string */ +const char *iwd_state_name_get(IWD_State state); + +#endif diff --git a/src/meson.build b/src/meson.build index 1c3dde0..cd275ec 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,15 +1,18 @@ 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_device.c', 'iwd/iwd_network.c', 'iwd/iwd_agent.c', + 'iwd/iwd_state.c', 'ui/wifi_auth.c', + 'ui/wifi_hidden.c', ) -# All core functionality now implemented +# All core functionality implemented module_deps = [ enlightenment, diff --git a/src/ui/wifi_hidden.c b/src/ui/wifi_hidden.c new file mode 100644 index 0000000..f08df75 --- /dev/null +++ b/src/ui/wifi_hidden.c @@ -0,0 +1,190 @@ +#include "../e_mod_main.h" + +/* Hidden network dialog structure */ +typedef struct _Hidden_Dialog +{ + Instance *inst; + E_Dialog *dialog; + Evas_Object *ssid_entry; + Evas_Object *pass_entry; + char *ssid; + char *passphrase; + Eina_Bool has_password; +} Hidden_Dialog; + +/* Global hidden dialog */ +static Hidden_Dialog *hidden_dialog = NULL; + +/* 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_dialog_show(Instance *inst) +{ + Hidden_Dialog *hd; + E_Dialog *dia; + Evas_Object *o, *list, *ssid_entry, *pass_entry; + + if (!inst) return; + + /* Only one hidden dialog at a time */ + if (hidden_dialog) + { + WRN("Hidden network dialog already open"); + return; + } + + DBG("Showing hidden network dialog"); + + hd = E_NEW(Hidden_Dialog, 1); + if (!hd) return; + + hd->inst = inst; + hidden_dialog = hd; + + /* Create dialog */ + dia = e_dialog_new(NULL, "E", "iwd_hidden_network"); + if (!dia) + { + _hidden_dialog_free(hd); + return; + } + + hd->dialog = dia; + + e_dialog_title_set(dia, "Connect to Hidden Network"); + e_dialog_icon_set(dia, "network-wireless", 48); + + /* Create content list */ + list = e_widget_list_add(evas_object_evas_get(dia->win), 0, 0); + + /* SSID label and entry */ + o = e_widget_label_add(evas_object_evas_get(dia->win), "Network Name (SSID):"); + e_widget_list_object_append(list, o, 1, 1, 0.5); + + ssid_entry = e_widget_entry_add(evas_object_evas_get(dia->win), &hd->ssid, NULL, NULL, NULL); + e_widget_size_min_set(ssid_entry, 280, 30); + e_widget_list_object_append(list, ssid_entry, 1, 1, 0.5); + hd->ssid_entry = ssid_entry; + + /* Spacing */ + o = e_widget_label_add(evas_object_evas_get(dia->win), " "); + e_widget_list_object_append(list, o, 1, 1, 0.5); + + /* Passphrase label and entry */ + o = e_widget_label_add(evas_object_evas_get(dia->win), "Passphrase (leave empty for open network):"); + e_widget_list_object_append(list, o, 1, 1, 0.5); + + pass_entry = e_widget_entry_add(evas_object_evas_get(dia->win), &hd->passphrase, NULL, NULL, NULL); + e_widget_entry_password_set(pass_entry, 1); + e_widget_size_min_set(pass_entry, 280, 30); + e_widget_list_object_append(list, pass_entry, 1, 1, 0.5); + hd->pass_entry = pass_entry; + + e_dialog_content_set(dia, list, 300, 180); + + /* Buttons */ + e_dialog_button_add(dia, "Connect", NULL, _hidden_dialog_ok_cb, hd); + e_dialog_button_add(dia, "Cancel", NULL, _hidden_dialog_cancel_cb, hd); + + e_dialog_button_focus_num(dia, 0); + e_dialog_show(dia); + + INF("Hidden network dialog shown"); +} + +/* OK button callback */ +static void +_hidden_dialog_ok_cb(void *data, E_Dialog *dialog EINA_UNUSED) +{ + Hidden_Dialog *hd = data; + + if (!hd) return; + + DBG("Hidden network dialog OK clicked"); + + if (!hd->ssid || strlen(hd->ssid) == 0) + { + e_util_dialog_show("Error", "Please enter a network name (SSID)."); + return; + } + + /* Check if passphrase is provided */ + hd->has_password = (hd->passphrase && strlen(hd->passphrase) > 0); + + if (hd->has_password && strlen(hd->passphrase) < 8) + { + e_util_dialog_show("Error", + "Passphrase must be at least 8 characters long."); + return; + } + + /* Store passphrase if provided */ + if (hd->has_password) + { + iwd_agent_set_passphrase(hd->passphrase); + } + + /* Connect to hidden network */ + if (hd->inst && hd->inst->device) + { + INF("Connecting to hidden network: %s", hd->ssid); + iwd_device_connect_hidden(hd->inst->device, hd->ssid); + } + + /* Close dialog */ + _hidden_dialog_free(hd); +} + +/* Cancel button callback */ +static void +_hidden_dialog_cancel_cb(void *data, E_Dialog *dialog EINA_UNUSED) +{ + Hidden_Dialog *hd = data; + + DBG("Hidden network dialog cancelled"); + + _hidden_dialog_free(hd); +} + +/* Free hidden dialog */ +static void +_hidden_dialog_free(Hidden_Dialog *hd) +{ + if (!hd) return; + + DBG("Freeing hidden network dialog"); + + if (hd->dialog) + e_object_del(E_OBJECT(hd->dialog)); + + /* Clear sensitive data from memory */ + if (hd->ssid) + { + memset(hd->ssid, 0, strlen(hd->ssid)); + E_FREE(hd->ssid); + } + + if (hd->passphrase) + { + memset(hd->passphrase, 0, strlen(hd->passphrase)); + E_FREE(hd->passphrase); + } + + E_FREE(hd); + hidden_dialog = NULL; +} + +/* Cancel any open hidden dialog */ +void +wifi_hidden_dialog_cancel(void) +{ + if (hidden_dialog) + { + DBG("Cancelling hidden network dialog from external request"); + _hidden_dialog_free(hidden_dialog); + } +} diff --git a/src/ui/wifi_hidden.h b/src/ui/wifi_hidden.h new file mode 100644 index 0000000..5e9c4b1 --- /dev/null +++ b/src/ui/wifi_hidden.h @@ -0,0 +1,15 @@ +#ifndef WIFI_HIDDEN_H +#define WIFI_HIDDEN_H + +#include + +/* Forward declarations */ +typedef struct _Instance Instance; + +/* Show hidden network connection dialog */ +void wifi_hidden_dialog_show(Instance *inst); + +/* Cancel/close hidden network dialog */ +void wifi_hidden_dialog_cancel(void); + +#endif