diff options
author | Dan Williams <dcbw@redhat.com> | 2010-03-13 16:26:46 -0800 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2010-03-13 16:26:46 -0800 |
commit | ac7310ab1050701c07705e548c408d97aea76636 (patch) | |
tree | 98ae68fa1ee493bc3bc2caaa6fb38291355742fc /src | |
parent | 429c7cc661780d2848a97b092e1a0023e8c4d603 (diff) |
gsm: add character set get/set support
Diffstat (limited to 'src')
-rw-r--r-- | src/mm-errors.c | 1 | ||||
-rw-r--r-- | src/mm-errors.h | 3 | ||||
-rw-r--r-- | src/mm-generic-gsm.c | 354 | ||||
-rw-r--r-- | src/mm-modem.c | 93 | ||||
-rw-r--r-- | src/mm-modem.h | 35 |
5 files changed, 466 insertions, 20 deletions
diff --git a/src/mm-errors.c b/src/mm-errors.c index d0a71d65..c0ed608d 100644 --- a/src/mm-errors.c +++ b/src/mm-errors.c @@ -77,6 +77,7 @@ mm_modem_error_get_type (void) ENUM_ENTRY (MM_MODEM_ERROR_OPERATION_IN_PROGRESS, "OperationInProgress"), ENUM_ENTRY (MM_MODEM_ERROR_REMOVED, "Removed"), ENUM_ENTRY (MM_MODEM_ERROR_AUTHORIZATION_REQUIRED, "AuthorizationRequired"), + ENUM_ENTRY (MM_MODEM_ERROR_UNSUPPORTED_CHARSET, "UnsupportedCharset"), { 0, 0, 0 } }; diff --git a/src/mm-errors.h b/src/mm-errors.h index 1b924d40..15ac773b 100644 --- a/src/mm-errors.h +++ b/src/mm-errors.h @@ -40,7 +40,8 @@ enum { MM_MODEM_ERROR_DISCONNECTED = 3, MM_MODEM_ERROR_OPERATION_IN_PROGRESS = 4, MM_MODEM_ERROR_REMOVED = 5, - MM_MODEM_ERROR_AUTHORIZATION_REQUIRED = 6 + MM_MODEM_ERROR_AUTHORIZATION_REQUIRED = 6, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET = 7 }; #define MM_MODEM_ERROR (mm_modem_error_quark ()) diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c index 0d55431d..27c09dbf 100644 --- a/src/mm-generic-gsm.c +++ b/src/mm-generic-gsm.c @@ -15,6 +15,7 @@ * Copyright (C) 2009 Ericsson */ +#include <config.h> #include <stdlib.h> #include <stdio.h> #include <string.h> @@ -84,6 +85,9 @@ typedef struct { guint32 signal_quality; gint cid; + guint32 charsets; + guint32 cur_charset; + MMAtSerialPort *primary; MMAtSerialPort *secondary; MMPort *data; @@ -644,10 +648,99 @@ creg2_done (MMAtSerialPort *port, } static void +enable_failed (MMModem *modem, GError *error, MMCallbackInfo *info) +{ + MMGenericGsmPrivate *priv; + + info->error = mm_modem_check_removed (modem, error); + + if (modem) { + mm_modem_set_state (modem, + MM_MODEM_STATE_DISABLED, + MM_MODEM_STATE_REASON_NONE); + + priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + + if (priv->primary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->primary))) + mm_serial_port_close (MM_SERIAL_PORT (priv->primary)); + if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) + mm_serial_port_close (MM_SERIAL_PORT (priv->secondary)); + } + + mm_callback_info_schedule (info); +} + +static guint32 best_charsets[] = { + MM_MODEM_CHARSET_UTF8, + MM_MODEM_CHARSET_UCS2, + MM_MODEM_CHARSET_8859_1, + MM_MODEM_CHARSET_IRA, + MM_MODEM_CHARSET_UNKNOWN +}; + +static void +enabled_set_charset_done (MMModem *modem, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + guint idx; + + /* only modem removals are really a hard error */ + if (error) { + if (!modem) { + enable_failed (modem, error, info); + return; + } + + /* Try the next best charset */ + idx = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "best-charset")) + 1; + if (best_charsets[idx] == MM_MODEM_CHARSET_UNKNOWN) { + GError *tmp_error; + + /* No more character sets we can use */ + tmp_error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET, + "Failed to find a usable modem character set"); + enable_failed (modem, tmp_error, info); + g_error_free (tmp_error); + } else { + /* Send the new charset */ + mm_callback_info_set_data (info, "best-charset", GUINT_TO_POINTER (idx), NULL); + mm_modem_set_charset (modem, best_charsets[idx], enabled_set_charset_done, info); + } + } else { + /* Modem is now enabled; update the state */ + mm_generic_gsm_update_enabled_state (MM_GENERIC_GSM (modem), FALSE, MM_MODEM_STATE_REASON_NONE); + + /* Set up unsolicited registration notifications */ + mm_at_serial_port_queue_command (MM_GENERIC_GSM_GET_PRIVATE (modem)->primary, + "+CREG=2", 3, creg2_done, info); + } +} + +static void +supported_charsets_done (MMModem *modem, + guint32 charsets, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (!modem) { + enable_failed (modem, error, info); + return; + } + + /* Switch the device's charset; we prefer UTF-8, but UCS2 will do too */ + mm_modem_set_charset (modem, MM_MODEM_CHARSET_UTF8, enabled_set_charset_done, info); +} + +static void get_allowed_mode_done (MMModem *modem, - MMModemGsmAllowedMode mode, - GError *error, - gpointer user_data) + MMModemGsmAllowedMode mode, + GError *error, + gpointer user_data) { if (modem) { mm_generic_gsm_update_allowed_mode (MM_GENERIC_GSM (modem), @@ -669,21 +762,8 @@ mm_generic_gsm_enable_complete (MMGenericGsm *self, priv = MM_GENERIC_GSM_GET_PRIVATE (self); if (error) { - mm_modem_set_state (MM_MODEM (self), - MM_MODEM_STATE_DISABLED, - MM_MODEM_STATE_REASON_NONE); - - if (priv->primary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->primary))) - mm_serial_port_close (MM_SERIAL_PORT (priv->primary)); - if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) - mm_serial_port_close (MM_SERIAL_PORT (priv->secondary)); - - info->error = g_error_copy (error); - mm_callback_info_schedule (info); + enable_failed ((MMModem *) self, error, info); return; - } else { - /* Modem is enabled; update the state */ - mm_generic_gsm_update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE); } /* Open the second port here if the modem has one. We'll use it for @@ -709,8 +789,8 @@ mm_generic_gsm_enable_complete (MMGenericGsm *self, if (MM_GENERIC_GSM_GET_CLASS (self)->get_allowed_mode) MM_GENERIC_GSM_GET_CLASS (self)->get_allowed_mode (self, get_allowed_mode_done, NULL); - /* Set up unsolicited registration notifications */ - mm_at_serial_port_queue_command (priv->primary, "+CREG=2", 3, creg2_done, info); + /* And supported character sets */ + mm_modem_get_supported_charsets (MM_MODEM (self), supported_charsets_done, info); } static void @@ -2522,6 +2602,240 @@ set_allowed_mode (MMModemGsmNetwork *net, } /*****************************************************************************/ +/* Charset stuff */ + +static void +get_charsets_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv; + GRegex *r = NULL; + GMatchInfo *match_info; + const char *p; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + mm_callback_info_schedule (info); + return; + } + + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + /* Find the first '(' */ + p = strchr (response->str, '('); + if (!p) + goto done; + p++; + + /* Now parse each charset */ + r = g_regex_new ("\\s*([^,\\)]+)\\s*", 0, 0, NULL); + if (!r) + goto done; + + if (!g_regex_match_full (r, p, strlen (p), 0, 0, &match_info, NULL)) + goto done; + + priv->charsets = MM_MODEM_CHARSET_UNKNOWN; + + while (g_match_info_matches (match_info)) { + char *str; + + str = g_match_info_fetch (match_info, 1); + priv->charsets |= mm_modem_charset_from_string (str); + g_free (str); + + g_match_info_next (match_info, NULL); + } + g_match_info_free (match_info); + + mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->charsets), NULL); + +done: + if (!info->error && !priv->charsets) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Failed to parse the supported character sets response"); + } + + mm_callback_info_schedule (info); +} + +static void +get_supported_charsets (MMModem *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMCallbackInfo *info; + MMAtSerialPort *port = priv->primary; + + info = mm_callback_info_uint_new (MM_MODEM (self), callback, user_data); + + if (mm_port_get_connected (MM_PORT (priv->primary))) { + if (!priv->secondary) { + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, + "Cannot get serving system while connected"); + mm_callback_info_schedule (info); + return; + } + + /* Use secondary port if primary is connected */ + port = priv->secondary; + } + + /* Use cached value if we have one */ + if (MM_GENERIC_GSM_GET_PRIVATE (self)->charsets) { + mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->charsets), NULL); + mm_callback_info_schedule (info); + } else + mm_at_serial_port_queue_command (port, "+CSCS=?", 3, get_charsets_done, info); +} + +static void +set_get_charset_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv; + MMModemCharset tried_charset; + const char *p; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + mm_callback_info_schedule (info); + return; + } + + p = response->str; + if (g_str_has_prefix (p, "+CSCS:")) + p += 6; + while (*p == ' ') + p++; + + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + priv->cur_charset = mm_modem_charset_from_string (p); + + tried_charset = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "charset")); + + if (tried_charset != priv->cur_charset) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET, + "Modem failed to change character set to %s", + mm_modem_charset_to_string (tried_charset)); + } + + mm_callback_info_schedule (info); +} + +#define TRIED_NO_QUOTES_TAG "tried-no-quotes" + +static void +set_charset_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + gboolean tried_no_quotes = !!mm_callback_info_get_data (info, TRIED_NO_QUOTES_TAG); + MMModemCharset charset = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "charset")); + char *command; + + if (!info->modem || tried_no_quotes) { + mm_callback_info_schedule (info); + return; + } + + /* Some modems puke if you include the quotes around the character + * set name, so lets try it again without them. + */ + mm_callback_info_set_data (info, TRIED_NO_QUOTES_TAG, GUINT_TO_POINTER (TRUE), NULL); + command = g_strdup_printf ("+CSCS=%s", mm_modem_charset_to_string (charset)); + mm_at_serial_port_queue_command (port, command, 3, set_charset_done, info); + g_free (command); + } else + mm_at_serial_port_queue_command (port, "+CSCS?", 3, set_get_charset_done, info); +} + +static gboolean +check_for_single_value (guint32 value) +{ + gboolean found = FALSE; + guint32 i; + + for (i = 1; i <= 32; i++) { + if (value & 0x1) { + if (found) + return FALSE; /* More than one bit set */ + found = TRUE; + } + value >>= 1; + } + + return TRUE; +} + +static void +set_charset (MMModem *modem, + MMModemCharset charset, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + const char *str; + char *command; + MMAtSerialPort *port = priv->primary; + + info = mm_callback_info_new (modem, callback, user_data); + + if (!(priv->charsets & charset) || !check_for_single_value (charset)) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET, + "Character set 0x%X not supported", + charset); + mm_callback_info_schedule (info); + return; + } + + str = mm_modem_charset_to_string (charset); + if (!str) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET, + "Unhandled character set 0x%X", + charset); + mm_callback_info_schedule (info); + return; + } + + if (mm_port_get_connected (MM_PORT (priv->primary))) { + if (!priv->secondary) { + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, + "Cannot get set character set while connected"); + mm_callback_info_schedule (info); + return; + } + + /* Use secondary port if primary is connected */ + port = priv->secondary; + } + + mm_callback_info_set_data (info, "charset", GUINT_TO_POINTER (charset), NULL); + + command = g_strdup_printf ("+CSCS=\"%s\"", str); + mm_at_serial_port_queue_command (port, command, 3, set_charset_done, info); + g_free (command); +} + +/*****************************************************************************/ /* MMModemGsmSms interface */ static void @@ -2877,6 +3191,8 @@ modem_init (MMModem *modem_class) modem_class->connect = connect; modem_class->disconnect = disconnect; modem_class->get_info = get_card_info; + modem_class->get_supported_charsets = get_supported_charsets; + modem_class->set_charset = set_charset; } static void diff --git a/src/mm-modem.c b/src/mm-modem.c index 8e2d8a48..6446e16d 100644 --- a/src/mm-modem.c +++ b/src/mm-modem.c @@ -14,6 +14,7 @@ * Copyright (C) 2009 - 2010 Red Hat, Inc. */ +#include <config.h> #include <string.h> #include <dbus/dbus-glib.h> #include "mm-modem.h" @@ -471,6 +472,98 @@ impl_modem_get_info (MMModem *modem, /*****************************************************************************/ +void +mm_modem_get_supported_charsets (MMModem *self, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->get_supported_charsets) + MM_MODEM_GET_INTERFACE (self)->get_supported_charsets (self, callback, user_data); + else { + info = mm_callback_info_uint_new (MM_MODEM (self), callback, user_data); + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + mm_callback_info_schedule (info); + } +} + +void +mm_modem_set_charset (MMModem *self, + MMModemCharset charset, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + g_return_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN); + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->set_charset) + MM_MODEM_GET_INTERFACE (self)->set_charset (self, charset, callback, user_data); + else { + info = mm_callback_info_new (MM_MODEM (self), callback, user_data); + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + mm_callback_info_schedule (info); + } +} + +typedef struct { + const char *name; + MMModemCharset charset; +} CharsetEntry; + +static CharsetEntry charset_map[] = { + { "UTF-8", MM_MODEM_CHARSET_UTF8 }, + { "UCS2", MM_MODEM_CHARSET_UCS2 }, + { "IRA", MM_MODEM_CHARSET_IRA }, + { "GSM", MM_MODEM_CHARSET_GSM }, + { "8859-1", MM_MODEM_CHARSET_8859_1 }, + { "PCCP437", MM_MODEM_CHARSET_PCCP437 }, + { "PCDN", MM_MODEM_CHARSET_PCDN }, + { "HEX", MM_MODEM_CHARSET_HEX }, + { NULL, MM_MODEM_CHARSET_UNKNOWN } +}; + +const char * +mm_modem_charset_to_string (MMModemCharset charset) +{ + CharsetEntry *iter = &charset_map[0]; + + g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL); + + while (iter->name) { + if (iter->charset == charset) + return iter->name; + iter++; + } + g_warn_if_reached (); + return NULL; +} + +MMModemCharset +mm_modem_charset_from_string (const char *string) +{ + CharsetEntry *iter = &charset_map[0]; + + g_return_val_if_fail (string != NULL, MM_MODEM_CHARSET_UNKNOWN); + + while (iter->name) { + if (strcasestr (string, iter->name)) + return iter->charset; + iter++; + } + return MM_MODEM_CHARSET_UNKNOWN; +} + +/*****************************************************************************/ + gboolean mm_modem_owns_port (MMModem *self, const char *subsys, diff --git a/src/mm-modem.h b/src/mm-modem.h index 93915eb0..5fc4bdcb 100644 --- a/src/mm-modem.h +++ b/src/mm-modem.h @@ -42,6 +42,18 @@ typedef enum { MM_MODEM_STATE_REASON_NONE = 0 } MMModemStateReason; +typedef enum { + MM_MODEM_CHARSET_UNKNOWN = 0x00000000, + MM_MODEM_CHARSET_GSM = 0x00000001, + MM_MODEM_CHARSET_IRA = 0x00000002, + MM_MODEM_CHARSET_8859_1 = 0x00000004, + MM_MODEM_CHARSET_UTF8 = 0x00000008, + MM_MODEM_CHARSET_UCS2 = 0x00000010, + MM_MODEM_CHARSET_PCCP437 = 0x00000020, + MM_MODEM_CHARSET_PCDN = 0x00000040, + MM_MODEM_CHARSET_HEX = 0x00000080 +} MMModemCharset; + #define DBUS_PATH_TAG "dbus-path" #define MM_TYPE_MODEM (mm_modem_get_type ()) @@ -158,6 +170,16 @@ struct _MMModem { MMModemInfoFn callback, gpointer user_data); + void (*get_supported_charsets) (MMModem *self, + MMModemUIntFn callback, + gpointer user_data); + + void (*set_charset) (MMModem *self, + MMModemCharset charset, + MMModemFn callback, + gpointer user_data); + + /* Normally implemented by the modem base class; plugins should * never need to implement this. */ @@ -222,6 +244,19 @@ void mm_modem_get_info (MMModem *self, MMModemInfoFn callback, gpointer user_data); +void mm_modem_get_supported_charsets (MMModem *self, + MMModemUIntFn callback, + gpointer user_data); + +void mm_modem_set_charset (MMModem *self, + MMModemCharset charset, + MMModemFn callback, + gpointer user_data); + +const char *mm_modem_charset_to_string (MMModemCharset charset); + +MMModemCharset mm_modem_charset_from_string (const char *string); + gboolean mm_modem_get_valid (MMModem *self); char *mm_modem_get_device (MMModem *self); |