diff --git a/src/e_mod_gadget.c b/src/e_mod_gadget.c new file mode 100644 index 0000000..2c98a07 --- /dev/null +++ b/src/e_mod_gadget.c @@ -0,0 +1,274 @@ +#include "e_mod_main.h" + +/* 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); + +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 = +{ + 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 +}; + +/* Global gadcon provider */ +static E_Gadcon_Client_Class *gadcon_class = NULL; + +/* Initialize gadget subsystem */ +void +e_iwd_gadget_init(void) +{ + DBG("Initializing gadget"); + + gadcon_class = (E_Gadcon_Client_Class *)&_gc_class; + e_gadcon_provider_register(gadcon_class); +} + +/* Shutdown gadget subsystem */ +void +e_iwd_gadget_shutdown(void) +{ + DBG("Shutting down gadget"); + + if (gadcon_class) + { + e_gadcon_provider_unregister(gadcon_class); + gadcon_class = NULL; + } +} + +/* Gadcon init */ +static E_Gadcon_Client * +_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style) +{ + Instance *inst; + E_Gadcon_Client *gcc; + Evas_Object *o; + + DBG("Creating gadget instance"); + + inst = E_NEW(Instance, 1); + if (!inst) return NULL; + + /* Create gadcon client */ + gcc = e_gadcon_client_new(gc, name, id, style, NULL); + if (!gcc) + { + E_FREE(inst); + return NULL; + } + + gcc->data = inst; + inst->gcc = gcc; + + /* Create icon */ + o = edje_object_add(gcc->gadcon->evas); + 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); + evas_object_resize(o, 16, 16); + evas_object_show(o); + + /* Add mouse event handler */ + evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, + _gadget_mouse_down_cb, inst); + + /* Set gadcon object */ + e_gadcon_client_min_size_set(gcc, 16, 16); + e_gadcon_client_aspect_set(gcc, 16, 16); + e_gadcon_client_show(gcc); + + /* 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; + + 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) +{ + e_gadcon_client_aspect_set(gcc, 16, 16); + e_gadcon_client_min_size_set(gcc, 16, 16); +} + +/* Gadcon label */ +static const char * +_gc_label(const E_Gadcon_Client_Class *client_class EINA_UNUSED) +{ + 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; + + 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); + evas_object_resize(o, 16, 16); + + return o; +} + +/* Generate new ID */ +static const char * +_gc_id_new(const E_Gadcon_Client_Class *client_class EINA_UNUSED) +{ + static char buf[32]; + snprintf(buf, sizeof(buf), "%s.%d", _gc_class.name, rand()); + return buf; +} + +/* Mouse down callback */ +static void +_gadget_mouse_down_cb(void *data, Evas *e EINA_UNUSED, + Evas_Object *obj EINA_UNUSED, + void *event_info) +{ + Instance *inst = data; + Evas_Event_Mouse_Down *ev = event_info; + + if (!inst) return; + + if (ev->button == 1) /* Left click */ + { + if (inst->popup) + { + /* Close popup */ + iwd_popup_del(inst); + } + else + { + /* Open popup */ + iwd_popup_new(inst); + } + } +} + +/* Update gadget icon and tooltip */ +static void +_gadget_update(Instance *inst) +{ + 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"); + } + + /* 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) + { + 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 */ + } +} + +/* Update timer callback */ +static Eina_Bool +_gadget_update_timer_cb(void *data) +{ + Instance *inst = data; + + _gadget_update(inst); + + return ECORE_CALLBACK_RENEW; +} diff --git a/src/e_mod_main.c b/src/e_mod_main.c index 5864c7d..6122639 100644 --- a/src/e_mod_main.c +++ b/src/e_mod_main.c @@ -219,15 +219,4 @@ _iwd_config_free(void) mod->conf = NULL; } -/* Stub implementations for Phase 3 functions */ -void -e_iwd_gadget_init(void) -{ - DBG("Gadget initialization (stub - will be implemented in Phase 3)"); -} - -void -e_iwd_gadget_shutdown(void) -{ - DBG("Gadget shutdown (stub)"); -} +/* Gadget implementations are in e_mod_gadget.c */ diff --git a/src/e_mod_main.h b/src/e_mod_main.h index 2bdce4c..1d7cafb 100644 --- a/src/e_mod_main.h +++ b/src/e_mod_main.h @@ -11,6 +11,9 @@ #define MOD_CONFIG_FILE_VERSION \ ((MOD_CONFIG_FILE_EPOCH << 16) | MOD_CONFIG_FILE_GENERATION) +/* Forward declarations for iwd types */ +typedef struct _IWD_Device IWD_Device; + /* Configuration structure */ typedef struct _Config { @@ -21,8 +24,17 @@ typedef struct _Config const char *preferred_adapter; } Config; -/* Module instance structure */ -typedef struct _Instance Instance; +/* 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 */ + + IWD_Device *device; + Ecore_Timer *update_timer; +} Instance; /* Global module context */ typedef struct _Mod @@ -63,10 +75,14 @@ E_API int e_modapi_save(E_Module *m); void e_iwd_config_init(void); void e_iwd_config_shutdown(void); -/* Gadget functions (will be implemented in Phase 3) */ +/* 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); + /* D-Bus functions */ #include "iwd/iwd_dbus.h" #include "iwd/iwd_device.h" diff --git a/src/e_mod_popup.c b/src/e_mod_popup.c new file mode 100644 index 0000000..1c01417 --- /dev/null +++ b/src/e_mod_popup.c @@ -0,0 +1,238 @@ +#include "e_mod_main.h" + +/* 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 Eina_Bool _popup_reopen_cb(void *data); + +/* Create popup */ +void +iwd_popup_new(Instance *inst) +{ + Evas_Object *list, *box, *button, *label; + E_Gadcon_Popup *popup; + Evas_Coord w, h; + IWD_Network *net; + Eina_List *l; + + if (!inst) return; + if (inst->popup) return; + + DBG("Creating popup"); + + /* Create popup */ + popup = e_gadcon_popup_new(inst->gcc, 0); + if (!popup) return; + + inst->popup = (void *)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); + + /* Title */ + label = elm_label_add(box); + elm_object_text_set(label, "IWD Wi-Fi Manager"); + elm_box_pack_end(box, label); + evas_object_show(label); + + /* Current connection status */ + if (inst->device) + { + Evas_Object *frame = elm_frame_add(box); + elm_object_text_set(frame, "Current Connection"); + + Evas_Object *status_box = elm_box_add(frame); + + char buf[256]; + if (inst->device->state && strcmp(inst->device->state, "connected") == 0) + { + snprintf(buf, sizeof(buf), "Connected to: %s", + inst->device->name ? inst->device->name : "Unknown"); + } + else if (inst->device->state && strcmp(inst->device->state, "connecting") == 0) + { + snprintf(buf, sizeof(buf), "Connecting..."); + } + else + { + snprintf(buf, sizeof(buf), "Disconnected"); + } + + Evas_Object *status_label = elm_label_add(status_box); + elm_object_text_set(status_label, buf); + elm_box_pack_end(status_box, status_label); + evas_object_show(status_label); + + /* Disconnect button if connected */ + if (inst->device->state && strcmp(inst->device->state, "connected") == 0) + { + button = elm_button_add(status_box); + elm_object_text_set(button, "Disconnect"); + evas_object_smart_callback_add(button, "clicked", _button_disconnect_cb, inst); + elm_box_pack_end(status_box, button); + evas_object_show(button); + } + + elm_object_content_set(frame, status_box); + evas_object_show(status_box); + elm_box_pack_end(box, frame); + evas_object_show(frame); + } + + /* Available networks */ + Evas_Object *frame = elm_frame_add(box); + elm_object_text_set(frame, "Available Networks"); + + list = elm_list_add(frame); + elm_list_mode_set(list, ELM_LIST_COMPRESS); + + /* Add networks to list */ + Eina_List *networks = iwd_networks_get(); + int count = 0; + + EINA_LIST_FOREACH(networks, l, net) + { + if (net->name) + { + char item_text[256]; + const char *security = "Open"; + + if (net->type) + { + if (strcmp(net->type, "psk") == 0) + security = "WPA2"; + else if (strcmp(net->type, "8021x") == 0) + security = "Enterprise"; + } + + snprintf(item_text, sizeof(item_text), "%s (%s)%s", + net->name, security, + net->known ? " *" : ""); + + elm_list_item_append(list, item_text, NULL, NULL, NULL, net); + count++; + } + } + + if (count == 0) + { + elm_list_item_append(list, "No networks found", NULL, NULL, NULL, NULL); + } + + 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); + + /* TODO: Add more buttons (enable/disable Wi-Fi, settings) */ + + 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); +} diff --git a/src/meson.build b/src/meson.build index 248bfc6..f674384 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,7 @@ module_sources = files( 'e_mod_main.c', + 'e_mod_gadget.c', + 'e_mod_popup.c', 'iwd/iwd_dbus.c', 'iwd/iwd_device.c', 'iwd/iwd_network.c', @@ -7,7 +9,7 @@ module_sources = files( ) # TODO: Add more source files as they are created in later phases -# Phase 3: e_mod_gadget.c, e_mod_popup.c, ui/*.c files +# Phase 4: ui/wifi_auth.c for passphrase dialog module_deps = [ enlightenment,