diff --git a/.gitignore b/.gitignore index ecce0115..3d983cab 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ src/parser/scanner.cxx src/sdp/sdp_parser.cxx src/sdp/sdp_parser.hxx src/sdp/sdp_scanner.cxx +/build # Qt Creator files CMakeLists.txt.user* diff --git a/CMakeLists.txt b/CMakeLists.txt index eab3902d..84e53843 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 2.8.10...3.22 FATAL_ERROR) project(twinkle) -set(PRODUCT_VERSION "1.10.3") -set(PRODUCT_DATE "February 18, 2022") +set(PRODUCT_VERSION "1.11.0-beta") +set(PRODUCT_DATE "March 12, 2024") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") # Add -DDEBUG for non-release builds, or uCommon will unilaterally define NDEBUG # (https://lists.gnu.org/archive/html/bug-commoncpp/2019-12/msg00000.html) diff --git a/src/gui/buddylistview.cpp b/src/gui/buddylistview.cpp index 38d89ae5..8654cdce 100644 --- a/src/gui/buddylistview.cpp +++ b/src/gui/buddylistview.cpp @@ -26,26 +26,50 @@ #include "qsize.h" #include -void AbstractBLVItem::set_icon(t_presence_state::t_basic_state state) { - switch (state) { - case t_presence_state::ST_BASIC_UNKNOWN: - setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_unknown.png")); + +void AbstractBLVItem::set_icon( t_presence_state::t_basic_state basic_state, + t_presence_state::t_user_state user_state) +{ + switch (user_state) + { + case t_presence_state::ST_USER_TALKING: + setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_talking.png")); break; - case t_presence_state::ST_BASIC_CLOSED: - setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_offline.png")); + case t_presence_state::ST_USER_AFK: + setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_afk.png")); break; - case t_presence_state::ST_BASIC_OPEN: - setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_online.png")); + case t_presence_state::ST_USER_BUSY: + setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_busy.png")); break; - case t_presence_state::ST_BASIC_FAILED: - setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_failed.png")); + case t_presence_state::ST_USER_ONLINE: + setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_online.png")); break; - case t_presence_state::ST_BASIC_REJECTED: - setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_rejected.png")); + case t_presence_state::ST_USER_OFFLINE: + setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_offline.png")); break; default: - setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_unknown.png")); - break; + // fallback to SIP basic states + switch (basic_state) + { + case t_presence_state::ST_BASIC_UNKNOWN: + setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_unknown.png")); + break; + case t_presence_state::ST_BASIC_CLOSED: + setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_offline.png")); + break; + case t_presence_state::ST_BASIC_OPEN: + setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_online.png")); + break; + case t_presence_state::ST_BASIC_FAILED: + setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_failed.png")); + break; + case t_presence_state::ST_BASIC_REJECTED: + setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_rejected.png")); + break; + default: + setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_unknown.png")); + break; + } } } @@ -78,7 +102,9 @@ void BuddyListViewItem::set_icon(void) { QString failure; t_presence_state::t_basic_state basic_state = buddy-> get_presence_state()->get_basic_state(); - AbstractBLVItem::set_icon(basic_state); + t_presence_state::t_user_state user_state = buddy-> + get_presence_state()->get_user_state(); + AbstractBLVItem::set_icon(basic_state, user_state); tip += "
"; tip += ""; @@ -155,6 +181,7 @@ t_buddy *BuddyListViewItem::get_buddy(void) { void BLViewUserItem::set_icon(void) { t_presence_state::t_basic_state basic_state; + t_presence_state::t_user_state user_state; QString failure; QString profile_name = QString::fromStdString(presence_epa->get_user_profile()->get_profile_name()); @@ -180,7 +207,7 @@ void BLViewUserItem::set_icon(void) { break; case t_presence_epa::EPA_PUBLISHED: basic_state = presence_epa->get_basic_state(); - AbstractBLVItem::set_icon(basic_state); + AbstractBLVItem::set_icon(basic_state, t_presence_state::ST_USER_UNKNOWN); switch (presence_epa->get_basic_state()) { case t_presence_state::ST_BASIC_CLOSED: diff --git a/src/gui/buddylistview.h b/src/gui/buddylistview.h index 2283f918..2f573137 100644 --- a/src/gui/buddylistview.h +++ b/src/gui/buddylistview.h @@ -31,7 +31,8 @@ class AbstractBLVItem : public QTreeWidgetItem { QString tip; // Set the presence icon to reflect the presence state - virtual void set_icon(t_presence_state::t_basic_state state); + virtual void set_icon(t_presence_state::t_basic_state basic_state, + t_presence_state::t_user_state user_state); public: AbstractBLVItem(QTreeWidgetItem *parent, const QString &text); diff --git a/src/gui/icons.qrc b/src/gui/icons.qrc index f9d276a9..60eda618 100644 --- a/src/gui/icons.qrc +++ b/src/gui/icons.qrc @@ -98,6 +98,9 @@ images/presence.png images/presence_rejected.png images/presence_unknown.png + images/presence_afk.png + images/presence_busy.png + images/presence_talking.png images/print images/qt-logo.png images/redial-disabled.png diff --git a/src/gui/images/answer-disabled.png b/src/gui/images/answer-disabled.png index de1dafd7..2c280a87 100644 Binary files a/src/gui/images/answer-disabled.png and b/src/gui/images/answer-disabled.png differ diff --git a/src/gui/images/answer.png b/src/gui/images/answer.png index 6ecffafb..b0b12d30 100644 Binary files a/src/gui/images/answer.png and b/src/gui/images/answer.png differ diff --git a/src/gui/images/bye-disabled.png b/src/gui/images/bye-disabled.png index 8d99eb1d..d8b8c0c2 100644 Binary files a/src/gui/images/bye-disabled.png and b/src/gui/images/bye-disabled.png differ diff --git a/src/gui/images/bye.png b/src/gui/images/bye.png index 04cc8700..ac0b13c9 100644 Binary files a/src/gui/images/bye.png and b/src/gui/images/bye.png differ diff --git a/src/gui/images/dtmf-disabled.png b/src/gui/images/dtmf-disabled.png index c4a7a94c..803ba9b5 100644 Binary files a/src/gui/images/dtmf-disabled.png and b/src/gui/images/dtmf-disabled.png differ diff --git a/src/gui/images/dtmf.png b/src/gui/images/dtmf.png index 92df50f0..f537779b 100644 Binary files a/src/gui/images/dtmf.png and b/src/gui/images/dtmf.png differ diff --git a/src/gui/images/hold-disabled.png b/src/gui/images/hold-disabled.png index 1f8f5def..ae347bf6 100644 Binary files a/src/gui/images/hold-disabled.png and b/src/gui/images/hold-disabled.png differ diff --git a/src/gui/images/hold.png b/src/gui/images/hold.png index 6cd6db7c..eb32e0ac 100644 Binary files a/src/gui/images/hold.png and b/src/gui/images/hold.png differ diff --git a/src/gui/images/invite-disabled.png b/src/gui/images/invite-disabled.png index 6197d2f0..9863039d 100644 Binary files a/src/gui/images/invite-disabled.png and b/src/gui/images/invite-disabled.png differ diff --git a/src/gui/images/invite.png b/src/gui/images/invite.png index 765f2537..fa778b01 100644 Binary files a/src/gui/images/invite.png and b/src/gui/images/invite.png differ diff --git a/src/gui/images/missed-disabled.png b/src/gui/images/missed-disabled.png index a95583a0..2521f93d 100644 Binary files a/src/gui/images/missed-disabled.png and b/src/gui/images/missed-disabled.png differ diff --git a/src/gui/images/mute-disabled.png b/src/gui/images/mute-disabled.png index 3f351812..c09f8820 100644 Binary files a/src/gui/images/mute-disabled.png and b/src/gui/images/mute-disabled.png differ diff --git a/src/gui/images/mute.png b/src/gui/images/mute.png index f721deb3..570ee55d 100644 Binary files a/src/gui/images/mute.png and b/src/gui/images/mute.png differ diff --git a/src/gui/images/presence_afk.png b/src/gui/images/presence_afk.png new file mode 100644 index 00000000..0ee63087 Binary files /dev/null and b/src/gui/images/presence_afk.png differ diff --git a/src/gui/images/presence_busy.png b/src/gui/images/presence_busy.png new file mode 100644 index 00000000..8df5a521 Binary files /dev/null and b/src/gui/images/presence_busy.png differ diff --git a/src/gui/images/presence_offline.png b/src/gui/images/presence_offline.png index 28a37386..2f3f6ef0 100644 Binary files a/src/gui/images/presence_offline.png and b/src/gui/images/presence_offline.png differ diff --git a/src/gui/images/presence_online.png b/src/gui/images/presence_online.png index f42ff865..47173152 100644 Binary files a/src/gui/images/presence_online.png and b/src/gui/images/presence_online.png differ diff --git a/src/gui/images/presence_talking.png b/src/gui/images/presence_talking.png new file mode 100644 index 00000000..935adfe8 Binary files /dev/null and b/src/gui/images/presence_talking.png differ diff --git a/src/gui/images/redial-disabled.png b/src/gui/images/redial-disabled.png index 5bfbf9b1..640996ba 100644 Binary files a/src/gui/images/redial-disabled.png and b/src/gui/images/redial-disabled.png differ diff --git a/src/gui/images/redial.png b/src/gui/images/redial.png index 2e273cfa..6749d483 100644 Binary files a/src/gui/images/redial.png and b/src/gui/images/redial.png differ diff --git a/src/gui/images/redirect-disabled.png b/src/gui/images/redirect-disabled.png index 04348d5b..f1fd502b 100644 Binary files a/src/gui/images/redirect-disabled.png and b/src/gui/images/redirect-disabled.png differ diff --git a/src/gui/images/redirect.png b/src/gui/images/redirect.png index e99e1b8d..f44d39c3 100644 Binary files a/src/gui/images/redirect.png and b/src/gui/images/redirect.png differ diff --git a/src/gui/images/reject-disabled.png b/src/gui/images/reject-disabled.png index da19ffd1..d80c24f4 100644 Binary files a/src/gui/images/reject-disabled.png and b/src/gui/images/reject-disabled.png differ diff --git a/src/gui/images/reject.png b/src/gui/images/reject.png index dbffecd6..f11864f5 100644 Binary files a/src/gui/images/reject.png and b/src/gui/images/reject.png differ diff --git a/src/gui/images/transfer-disabled.png b/src/gui/images/transfer-disabled.png index 79a62efe..f3adad0e 100644 Binary files a/src/gui/images/transfer-disabled.png and b/src/gui/images/transfer-disabled.png differ diff --git a/src/gui/images/transfer.png b/src/gui/images/transfer.png index 77f296d1..992f6539 100644 Binary files a/src/gui/images/transfer.png and b/src/gui/images/transfer.png differ diff --git a/src/presence/pidf_body.cpp b/src/presence/pidf_body.cpp index 251fdb6b..27fab82a 100644 --- a/src/presence/pidf_body.cpp +++ b/src/presence/pidf_body.cpp @@ -26,6 +26,7 @@ #define PIDF_XML_VERSION "1.0" #define PIDF_NAMESPACE "urn:ietf:params:xml:ns:pidf" +#define SWYX_NAMESPACE "http://sip.lanphone.de/presence/" #define IS_PIDF_TAG(node, tag) IS_XML_TAG(node, tag, PIDF_NAMESPACE) @@ -56,6 +57,7 @@ bool t_pidf_xml_body::extract_status(void) { pres_entity.clear(); tuple_id.clear(); basic_status.clear(); + user_status.clear(); // Get presence entity xmlChar *prop_entity = xmlGetProp(root_element, BAD_CAST "entity"); @@ -114,14 +116,32 @@ void t_pidf_xml_body::process_pidf_status(xmlNode *status) { xmlNode *child = status->children; for (xmlNode *cur_node = child; cur_node; cur_node = cur_node->next) { + // Process user status + if (IS_XML_TAG(cur_node, "userstatus", SWYX_NAMESPACE)) { + process_pidf_userstatus(cur_node); + } + // Process status if (IS_PIDF_TAG(cur_node, "basic")) { process_pidf_basic(cur_node); - break; } } } +void t_pidf_xml_body::process_pidf_userstatus(xmlNode *userstatus) +{ + assert(userstatus); + + xmlNode *child = userstatus->children; + if (child && child->type == XML_TEXT_NODE) { + user_status = tolower((char *)child->content); + } else { + log_file->write_report(" element has no content.", + "t_pidf_xml_body::process_pidf_userstatus", + LOG_NORMAL, LOG_WARNING); + } +} + void t_pidf_xml_body::process_pidf_basic(xmlNode *basic) { assert(basic); @@ -188,6 +208,10 @@ string t_pidf_xml_body::get_tuple_id(void) const { return tuple_id; } +string t_pidf_xml_body::get_user_status(void) const { + return user_status; +} + string t_pidf_xml_body::get_basic_status(void) const { return basic_status; } @@ -202,6 +226,11 @@ void t_pidf_xml_body::set_tuple_id(const string &_tuple_id) { tuple_id = _tuple_id; } +void t_pidf_xml_body::set_user_status(const string &_user_status) { + clear_xml_doc(); + user_status = _user_status; +} + void t_pidf_xml_body::set_basic_status(const string &_basic_status) { clear_xml_doc(); basic_status = _basic_status; diff --git a/src/presence/pidf_body.h b/src/presence/pidf_body.h index 8d67dcdb..712f3d7d 100644 --- a/src/presence/pidf_body.h +++ b/src/presence/pidf_body.h @@ -29,13 +29,20 @@ #define PIDF_STATUS_BASIC_OPEN "open" #define PIDF_STATUS_BASIC_CLOSED "closed" +#define PIDF_STATUS_USER_ONLINE "logged on" +#define PIDF_STATUS_USER_OFFLINE "logged off" +#define PIDF_STATUS_USER_TALKING "active" +#define PIDF_STATUS_USER_AFK "away" +#define PIDF_STATUS_USER_BUSY "donotdisturb" +#define PIDF_STATUS_USER_UNKNOWN "UNKNOWN" /** RFC 3863 pidf+xml body */ class t_pidf_xml_body : public t_sip_body_xml { private: - string pres_entity; /**< Presence entity */ - string tuple_id; /**< Id of tuple containing the basic status. */ + string pres_entity; /**< Presence entity */ + string tuple_id; /**< Id of tuple containing the basic status. */ string basic_status; /**< Value of basic-tag */ + string user_status; /**< Vaue if ippbx:userstatus-tag */ /** * Extract the status information from a PIDF document. @@ -63,6 +70,12 @@ class t_pidf_xml_body : public t_sip_body_xml { */ void process_pidf_basic(xmlNode *basic); + /** + * Process userstatus element. + * @param userstatus [in] userstatus element. + */ + void process_pidf_userstatus(xmlNode *userstatus); + protected: /** * Create a pidf document from the values stored in the attributes. @@ -81,6 +94,7 @@ class t_pidf_xml_body : public t_sip_body_xml { //@{ string get_pres_entity(void) const; string get_tuple_id(void) const; + string get_user_status(void) const; string get_basic_status(void) const; //@} @@ -88,7 +102,8 @@ class t_pidf_xml_body : public t_sip_body_xml { //@{ void set_pres_entity(const string &_pres_entity); void set_tuple_id(const string &_tuple_id); - void set_basic_status(const string &_basic_status);; + void set_user_status(const string &_user_status); + void set_basic_status(const string &_basic_status); //@} /** diff --git a/src/presence/presence_state.cpp b/src/presence/presence_state.cpp index b93c5648..81d95681 100644 --- a/src/presence/presence_state.cpp +++ b/src/presence/presence_state.cpp @@ -39,6 +39,23 @@ string t_presence_state::basic_state2str(t_presence_state::t_basic_state state) } } +string t_presence_state::user_state2str(t_presence_state::t_user_state state) { + switch (state) { + case ST_USER_BUSY: + return "busy"; + case ST_USER_AFK: + return "afk"; + case ST_USER_OFFLINE: + return "offline"; + case ST_USER_ONLINE: + return "online"; + case ST_USER_TALKING: + return "talking"; + default: + return "UNKNOWN"; + } +} + string t_presence_state::basic_state2pidf_str(t_presence_state::t_basic_state state) { if (state == ST_BASIC_OPEN) { return PIDF_STATUS_BASIC_OPEN; @@ -48,13 +65,52 @@ string t_presence_state::basic_state2pidf_str(t_presence_state::t_basic_state st return PIDF_STATUS_BASIC_CLOSED; } +string t_presence_state::user_state2pidf_str(t_presence_state::t_user_state state) { + switch (state) { + case ST_USER_OFFLINE: + return PIDF_STATUS_USER_OFFLINE; + case ST_USER_ONLINE: + return PIDF_STATUS_USER_ONLINE; + case ST_USER_TALKING: + return PIDF_STATUS_USER_TALKING; + case ST_USER_AFK: + return PIDF_STATUS_USER_AFK; + case ST_USER_BUSY: + return PIDF_STATUS_USER_BUSY; + default: + // Convert all other states to "closed". + return PIDF_STATUS_BASIC_CLOSED; + } +} + +t_presence_state::t_user_state t_presence_state::pidf_str2user_state(string user_state) { + if (user_state == PIDF_STATUS_USER_OFFLINE) { + return ST_USER_OFFLINE; + } + else if (user_state == PIDF_STATUS_USER_ONLINE) { + return ST_USER_ONLINE; + } + else if (user_state == PIDF_STATUS_USER_TALKING) { + return ST_USER_TALKING; + } + else if (user_state == PIDF_STATUS_USER_AFK) { + return ST_USER_AFK; + } + else if (user_state == PIDF_STATUS_USER_BUSY) { + return ST_USER_BUSY; + } else { + return ST_USER_UNKNOWN; + } +} + t_presence_state::t_presence_state() { assert(false); } t_presence_state::t_presence_state(t_buddy *_buddy) : buddy(_buddy), - basic_state(ST_BASIC_UNKNOWN) + basic_state(ST_BASIC_UNKNOWN), + user_state(ST_USER_UNKNOWN) { } @@ -66,6 +122,14 @@ t_presence_state::t_basic_state t_presence_state::get_basic_state(void) const { return result; } +t_presence_state::t_user_state t_presence_state::get_user_state(void) const { + t_user_state result; + mtx_state.lock(); + result = user_state; + mtx_state.unlock(); + return result; +} + string t_presence_state::get_failure_msg(void) const { string result; mtx_state.lock(); @@ -74,6 +138,25 @@ string t_presence_state::get_failure_msg(void) const { return result; } +void t_presence_state::set_user_state(t_presence_state::t_user_state state) +{ + mtx_state.lock(); + user_state = state; + + log_file->write_header("t_presence_state::set_user_state", LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Presence state changed to: "); + log_file->write_raw(user_state2str(user_state)); + log_file->write_endl(); + log_file->write_raw(buddy->get_sip_address()); + log_file->write_endl(); + log_file->write_footer(); + + mtx_state.unlock(); + + // Notify the stat change to all observers of the buddy. + buddy->notify(); +} + void t_presence_state::set_basic_state(t_presence_state::t_basic_state state) { mtx_state.lock(); basic_state = state; diff --git a/src/presence/presence_state.h b/src/presence/presence_state.h index 47de4a4c..da99d4e6 100644 --- a/src/presence/presence_state.h +++ b/src/presence/presence_state.h @@ -42,6 +42,15 @@ class t_presence_state { ST_BASIC_FAILED, /**< Failed to determine basic state. */ ST_BASIC_REJECTED,/**< Subscription has been rejected. */ }; + + enum t_user_state { + ST_USER_UNKNOWN, /**< Presence state is unknown. */ + ST_USER_ONLINE, /**< Online and free. */ + ST_USER_OFFLINE, /**< Offline. */ + ST_USER_TALKING, /**< On the phone (active). */ + ST_USER_AFK, /**< Away (but available). */ + ST_USER_BUSY, /**< Busy (unavailable). */ + }; /** * Convert a basic state to a string representation for internal usage. @@ -49,6 +58,7 @@ class t_presence_state { * @return String representation of the basic state. */ static string basic_state2str(t_basic_state state); + static string user_state2str(t_user_state state); /** * Convert a basic state to a PIDF string representation. @@ -56,6 +66,8 @@ class t_presence_state { * @return PIDF representation of the basic state. */ static string basic_state2pidf_str(t_basic_state state); + static string user_state2pidf_str(t_user_state state); + t_user_state pidf_str2user_state(string user_state); private: /** Mutex for concurrent access to the presence state. */ @@ -66,6 +78,7 @@ class t_presence_state { /** Basic presence state. */ t_basic_state basic_state; + t_user_state user_state; /** Detailed failure message */ string failure_msg; @@ -79,12 +92,14 @@ class t_presence_state { /** @name Getters */ //@{ + t_user_state get_user_state(void) const; t_basic_state get_basic_state(void) const; string get_failure_msg(void) const; //@} /** @name Setters */ //@{ + void set_user_state(t_user_state state); void set_basic_state(t_basic_state state); void set_failure_msg(const string &msg); //@} diff --git a/src/presence/presence_subscription.cpp b/src/presence/presence_subscription.cpp index cd24dc3e..5f4f1d62 100644 --- a/src/presence/presence_subscription.cpp +++ b/src/presence/presence_subscription.cpp @@ -52,6 +52,9 @@ bool t_presence_subscription::recv_notify(t_request *r, t_tuid tuid, t_tid tid) { t_pidf_xml_body *body = dynamic_cast(r->body); assert(body); + + string user = body->get_user_status(); + presence_state->set_user_state(presence_state->pidf_str2user_state(user)); string basic = body->get_basic_status(); if (basic == PIDF_STATUS_BASIC_OPEN) {