/* $Id: phone.c,v 1.20 2010/04/27 14:31:19 khorben Exp $ */ /* Copyright (c) 2010 Pierre Pronchery */ /* This file is part of DeforaOS Desktop Phone */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include "gsm.h" #include "callbacks.h" #include "phone.h" #define _(string) gettext(string) /* Phone */ /* private */ /* types */ struct _Phone { GSM * gsm; guint ui_source; guint si_source; /* widgets */ PangoFontDescription * bold; /* code */ int en_code; GtkWidget * en_window; GtkWidget * en_entry; /* contacts */ GtkWidget * co_window; /* dialer */ GtkWidget * di_window; GtkWidget * di_entry; /* messages */ GtkWidget * me_window; /* systray */ GtkWidget * sy_level; }; /* prototypes */ static GtkWidget * _phone_create_dialpad(Phone * phone, char const * button1_image, char const * button1_label, GCallback button1_callback, char const * button2_image, char const * button2_label, GCallback button2_callback, GCallback button_callback); static void _phone_fetch_contacts(Phone * phone, unsigned int start, unsigned int end); static void _phone_set_signal_level(Phone * phone, gdouble level); static void _phone_set_status(Phone * phone, GSMStatus status); /* callbacks */ static void _phone_gsm_event(GSMEvent * event, gpointer data); static gboolean _phone_timeout_signal_level(gpointer data); /* public */ /* functions */ /* phone_new */ static gboolean _new_idle(gpointer data); Phone * phone_new(char const * device, unsigned int baudrate, int retry) { Phone * phone; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\", %u)\n", __func__, device, baudrate); #endif if((phone = malloc(sizeof(*phone))) == NULL) return NULL; if(device == NULL) device = "/dev/modem"; phone->gsm = gsm_new(device, baudrate); phone->ui_source = g_idle_add(_new_idle, phone); phone->si_source = 0; /* widgets */ phone->bold = pango_font_description_new(); pango_font_description_set_weight(phone->bold, PANGO_WEIGHT_BOLD); phone->en_code = -1; phone->en_window = NULL; phone->co_window = NULL; phone->di_window = NULL; phone->me_window = NULL; /* check errors */ if(phone->gsm == NULL) { phone_delete(phone); return NULL; } if(retry >= 0) gsm_set_retry(phone->gsm, retry); gsm_set_callback(phone->gsm, _phone_gsm_event, phone); return phone; } static gboolean _new_idle(gpointer data) { Phone * phone = data; phone_show_contacts(phone, FALSE); phone_show_dialer(phone, TRUE); phone_show_messages(phone, FALSE); phone->ui_source = 0; return FALSE; } /* phone_delete */ void phone_delete(Phone * phone) { if(phone->ui_source != 0) g_source_remove(phone->ui_source); if(phone->si_source != 0) g_source_remove(phone->si_source); pango_font_description_free(phone->bold); if(phone->gsm != NULL) gsm_delete(phone->gsm); free(phone); } /* useful */ /* phone_error */ static int _error_text(char const * message, int ret); int phone_error(Phone * phone, char const * message, int ret) { GtkWidget * dialog; if(phone == NULL) return _error_text(message, ret); dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", _("Error")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", message); gtk_window_set_title(GTK_WINDOW(dialog), _("Error")); g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK( gtk_widget_destroy), NULL); gtk_widget_show(dialog); return ret; } static int _error_text(char const * message, int ret) { fprintf(stderr, "phone: %s\n", message); return ret; } /* phone_call */ void phone_call(Phone * phone, char const * number) { if(number == NULL) number = gtk_entry_get_text(GTK_ENTRY(phone->di_entry)); if(number[0] == '\0') number = NULL; /* call the last number dialled */ gsm_call(phone->gsm, GSM_CALL_TYPE_VOICE, number); } /* phone_code_append */ void phone_code_append(Phone * phone, char character) { char const * text; size_t len; char * p; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%c)\n", __func__, character); #endif if((character < '0' || character > '9') && character != '*' && character != '+' && character != '#') return; text = gtk_entry_get_text(GTK_ENTRY(phone->en_entry)); len = strlen(text); if((p = malloc(len + 2)) == NULL) return; /* XXX report error */ snprintf(p, len + 2, "%s%c", text, character); gtk_entry_set_text(GTK_ENTRY(phone->en_entry), p); free(p); } /* phone_code_enter */ void phone_code_enter(Phone * phone, PhoneCode code) { phone->en_code = code; if(phone->en_window == NULL) phone_show_code(phone, FALSE); switch(code) { case PHONE_CODE_SIM_PIN: gtk_window_set_title(GTK_WINDOW(phone->en_window), _("Enter SIM code")); break; #ifndef DEBUG default: /* should not happen */ return; #endif } gtk_widget_show(phone->en_window); } /* phone_code_validate */ void phone_code_validate(Phone * phone) { PhoneCode code; char const * p; if(phone->en_code < 0) return; code = phone->en_code; switch(code) { case PHONE_CODE_SIM_PIN: p = gtk_entry_get_text(GTK_ENTRY(phone->en_entry)); gsm_enter_pin(phone->gsm, p); break; } } /* phone_dialer_append */ void phone_dialer_append(Phone * phone, char character) { char const * text; size_t len; char * p; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%c)\n", __func__, character); #endif if((character < '0' || character > '9') && character != '*' && character != '+' && character != '#') return; /* FIXME ask GSM if in a call; if yes, send DTMF */ text = gtk_entry_get_text(GTK_ENTRY(phone->di_entry)); len = strlen(text); if((p = malloc(len + 2)) == NULL) return; /* XXX report error */ snprintf(p, len + 2, "%s%c", text, character); gtk_entry_set_text(GTK_ENTRY(phone->di_entry), p); free(p); } /* phone_hangup */ void phone_hangup(Phone * phone) { gsm_hangup(phone->gsm); gtk_entry_set_text(GTK_ENTRY(phone->di_entry), ""); } /* phone_show_code */ void phone_show_code(Phone * phone, gboolean show) { GtkWidget * vbox; GtkWidget * hbox; /* XXX create in phone_create_dialpad? */ GtkWidget * widget; if(phone->en_window == NULL) { phone->en_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); vbox = gtk_vbox_new(FALSE, 0); hbox = gtk_hbox_new(FALSE, 0); phone->en_entry = gtk_entry_new(); gtk_entry_set_visibility(GTK_ENTRY(phone->en_entry), FALSE); gtk_widget_modify_font(phone->en_entry, phone->bold); g_signal_connect_swapped(G_OBJECT(phone->en_entry), "activate", G_CALLBACK(on_phone_code_enter), phone); gtk_box_pack_start(GTK_BOX(hbox), phone->en_entry, TRUE, TRUE, 2); widget = gtk_button_new(); gtk_button_set_image(GTK_BUTTON(widget), gtk_image_new_from_icon_name("edit-undo", GTK_ICON_SIZE_BUTTON)); gtk_button_set_relief(GTK_BUTTON(widget), GTK_RELIEF_NONE); /* FIXME connect callback */ gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 2); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 2); widget = _phone_create_dialpad(phone, GTK_STOCK_OK, _("Enter"), G_CALLBACK(on_phone_code_enter), GTK_STOCK_CANCEL, _("Skip"), G_CALLBACK(on_phone_code_leave), G_CALLBACK(on_phone_code_clicked)); gtk_box_pack_start(GTK_BOX(vbox), widget, TRUE, TRUE, 0); gtk_container_add(GTK_CONTAINER(phone->en_window), vbox); gtk_widget_show_all(vbox); } if(show) /* FIXME should only be shown with a valid code set */ gtk_widget_show(phone->en_window); /* XXX force focus? */ else gtk_widget_hide(phone->en_window); } /* phone_show_contacts */ void phone_show_contacts(Phone * phone, gboolean show) { if(phone->co_window == NULL) { phone->co_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(phone->co_window), _("Contacts")); g_signal_connect_swapped(G_OBJECT(phone->co_window), "delete-event", G_CALLBACK(on_phone_closex), phone->co_window); /* FIXME implement */ } if(show) gtk_widget_show(phone->co_window); /* XXX force focus? */ else gtk_widget_hide(phone->co_window); } /* phone_show_dialer */ void phone_show_dialer(Phone * phone, gboolean show) { GtkWidget * vbox; GtkWidget * hbox; GtkWidget * widget; if(phone->di_window == NULL) { phone->di_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(phone->di_window), _("Dialer")); g_signal_connect_swapped(G_OBJECT(phone->di_window), "delete-event", G_CALLBACK(on_phone_closex), phone->di_window); vbox = gtk_vbox_new(FALSE, 0); /* XXX signal level (place in systray) */ hbox = gtk_hbox_new(FALSE, 0); widget = gtk_label_new(_("Signal:")); gtk_widget_modify_font(widget, phone->bold); gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 2); phone->sy_level = gtk_progress_bar_new(); gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(phone->sy_level), 0.0); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(phone->sy_level), " "); gtk_box_pack_start(GTK_BOX(hbox), phone->sy_level, TRUE, TRUE, 2); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 2); /* entry */ hbox = gtk_hbox_new(FALSE, 0); phone->di_entry = gtk_entry_new(); gtk_widget_modify_font(phone->di_entry, phone->bold); g_signal_connect_swapped(G_OBJECT(phone->di_entry), "activate", G_CALLBACK(on_phone_dialer_call), phone); gtk_box_pack_start(GTK_BOX(hbox), phone->di_entry, TRUE, TRUE, 2); widget = gtk_button_new(); gtk_button_set_image(GTK_BUTTON(widget), gtk_image_new_from_icon_name( "stock_addressbook", GTK_ICON_SIZE_BUTTON)); gtk_button_set_relief(GTK_BUTTON(widget), GTK_RELIEF_NONE); g_signal_connect_swapped(G_OBJECT(widget), "clicked", G_CALLBACK(on_phone_contacts_show), phone); gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 2); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 2); /* dialpad */ widget = _phone_create_dialpad(phone, "call-start", _("Call"), G_CALLBACK(on_phone_dialer_call), "call-stop", _("Hang up"), G_CALLBACK(on_phone_dialer_hangup), G_CALLBACK(on_phone_dialer_clicked)); gtk_box_pack_start(GTK_BOX(vbox), widget, TRUE, TRUE, 0); gtk_container_add(GTK_CONTAINER(phone->di_window), vbox); gtk_widget_show_all(vbox); } if(show) gtk_widget_show(phone->di_window); /* XXX force focus? */ else gtk_widget_hide(phone->di_window); } /* phone_show_messages */ void phone_show_messages(Phone * phone, gboolean show) { if(phone->me_window == NULL) { phone->me_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(phone->me_window), _("Messages")); g_signal_connect_swapped(G_OBJECT(phone->me_window), "delete-event", G_CALLBACK(on_phone_closex), phone->me_window); /* FIXME implement */ } if(show) gtk_widget_show(phone->me_window); /* XXX force focus? */ else gtk_widget_hide(phone->me_window); } /* private */ /* phone_create_dialpad */ static GtkWidget * _phone_create_dialpad(Phone * phone, char const * button1_image, char const * button1_label, GCallback button1_callback, char const * button2_image, char const * button2_label, GCallback button2_callback, GCallback button_callback) { static struct { char character; char const * label; } numbers[12] = { { '1', "_1\n" }, { '2', "_2\nABC" }, { '3', "_3\nDEF" }, { '4', "_4\nGHI" }, { '5', "_5\nJKL" }, { '6', "_6\nMNO" }, { '7', "_7\nPQRS" }, { '8', "_8\nTUV" }, { '9', "_9\nWXYZ" }, { '*', "_*\n+" }, { '0', "_0\n" }, { '#', "_#\n" } }; GtkWidget * table; GtkWidget * button; GtkWidget * image; GtkWidget * label; int i; table = gtk_table_new(5, 6, TRUE); /* call */ button = gtk_button_new(); image = gtk_image_new_from_icon_name(button1_image, GTK_ICON_SIZE_BUTTON); gtk_button_set_image(GTK_BUTTON(button), image); gtk_button_set_label(GTK_BUTTON(button), button1_label); g_signal_connect_swapped(G_OBJECT(button), "clicked", button1_callback, phone); gtk_table_attach(GTK_TABLE(table), button, 0, 3, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 2, 2); button = gtk_button_new(); image = gtk_image_new_from_icon_name(button2_image, GTK_ICON_SIZE_BUTTON); gtk_button_set_image(GTK_BUTTON(button), image); gtk_button_set_label(GTK_BUTTON(button), button2_label); g_signal_connect_swapped(G_OBJECT(button), "clicked", button2_callback, phone); gtk_table_attach(GTK_TABLE(table), button, 3, 6, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 2, 2); /* numbers */ for(i = 0; i < 12; i++) { button = gtk_button_new(); label = gtk_label_new(NULL); gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER); gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), numbers[i].label); gtk_container_add(GTK_CONTAINER(button), label); g_object_set_data(G_OBJECT(button), "character", &numbers[i].character); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK( button_callback), phone); gtk_table_attach(GTK_TABLE(table), button, (i % 3) * 2, ((i % 3) + 1) * 2, (i / 3) + 1, (i / 3) + 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 2, 2); } return table; } /* phone_fetch_contacts */ static void _phone_fetch_contacts(Phone * phone, unsigned int start, unsigned int end) { unsigned int i; for(i = start; i < end; i+=10) { gsm_fetch_contacts(phone->gsm, start, i); start = i; } if(start < end) gsm_fetch_contacts(phone->gsm, start, end); } /* phone_set_signal_level */ static void _phone_set_signal_level(Phone * phone, gdouble level) { char buf[32]; if(level >= 0.0 && level <= 1.0) snprintf(buf, sizeof(buf), "%.0lf/10", level * 10); else { level = 0.0; snprintf(buf, sizeof(buf), "%s", _("Unknown")); } gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(phone->sy_level), level); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(phone->sy_level), buf); } /* phone_set_status */ static void _phone_set_status(Phone * phone, GSMStatus status) { switch(status) { case GSM_STATUS_INITIALIZED: /* XXX check SIM etc first */ gsm_fetch_contact_list(phone->gsm); gsm_fetch_message_list(phone->gsm); if(phone->si_source == 0) phone->si_source = g_timeout_add(2000, _phone_timeout_signal_level, phone); /* FIXME trigger registration */ break; case GSM_STATUS_REGISTERED: /* FIXME implement */ break; } } /* callbacks */ /* phone_gsm_event */ static void _phone_gsm_event(GSMEvent * event, gpointer data) { Phone * phone = data; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%d)\n", __func__, event->type); #endif switch(event->type) { case GSM_EVENT_TYPE_ERROR: if(event->error.error == GSM_ERROR_SIM_PIN_REQUIRED) phone_code_enter(phone, PHONE_CODE_SIM_PIN); else phone_error(phone, event->error.message, 0); break; case GSM_EVENT_TYPE_CONTACT_LIST: _phone_fetch_contacts(phone, event->contact_list.start, event->contact_list.end); break; case GSM_EVENT_TYPE_MESSAGE_LIST: _phone_fetch_contacts(phone, event->message_list.start, event->message_list.end); break; case GSM_EVENT_TYPE_SIGNAL_LEVEL: _phone_set_signal_level(phone, event->signal_level.level); break; case GSM_EVENT_TYPE_STATUS: _phone_set_status(phone, event->status.status); break; } } /* phone_timeout_signal_level */ static gboolean _phone_timeout_signal_level(gpointer data) { Phone * phone = data; gsm_fetch_signal_level(phone->gsm); return TRUE; }