diff --git a/images/icons/ic_keypad.svg b/images/icons/ic_keypad.svg
new file mode 100644
index 0000000000000000000000000000000000000000..c55cdf4fa7e5efbbf4ad08b01cea3a7655e64285
--- /dev/null
+++ b/images/icons/ic_keypad.svg
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<g>
+	<g>
+		<g>
+			<path d="M64,0C28.715,0,0,28.715,0,64s28.715,64,64,64s64-28.715,64-64S99.285,0,64,0z"/>
+			<path d="M64,192c-35.285,0-64,28.715-64,64s28.715,64,64,64s64-28.715,64-64S99.285,192,64,192z"/>
+			<path d="M64,384c-35.285,0-64,28.715-64,64c0,35.285,28.715,64,64,64s64-28.715,64-64C128,412.715,99.285,384,64,384z"/>
+			<path d="M256,0c-35.285,0-64,28.715-64,64s28.715,64,64,64s64-28.715,64-64S291.285,0,256,0z"/>
+			<path d="M256,192c-35.285,0-64,28.715-64,64s28.715,64,64,64s64-28.715,64-64S291.285,192,256,192z"/>
+			<path d="M256,384c-35.285,0-64,28.715-64,64c0,35.285,28.715,64,64,64s64-28.715,64-64C320,412.715,291.285,384,256,384z"/>
+			<path d="M448,128c35.285,0,64-28.715,64-64S483.285,0,448,0c-35.285,0-64,28.715-64,64S412.715,128,448,128z"/>
+			<path d="M448,192c-35.285,0-64,28.715-64,64s28.715,64,64,64c35.285,0,64-28.715,64-64S483.285,192,448,192z"/>
+			<path d="M448,384c-35.285,0-64,28.715-64,64c0,35.285,28.715,64,64,64c35.285,0,64-28.715,64-64C512,412.715,483.285,384,448,384
+				z"/>
+		</g>
+	</g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/images/icons/icon-keypad-24-2x.png b/images/icons/icon-keypad-24-2x.png
deleted file mode 100644
index 2b84863e648cf02bb13cbbdbe2e53168897fc5cd..0000000000000000000000000000000000000000
Binary files a/images/icons/icon-keypad-24-2x.png and /dev/null differ
diff --git a/images/icons/icon-keypad-24.png b/images/icons/icon-keypad-24.png
deleted file mode 100644
index 2abc9be80cb8393a2357c49383663951b5a4b88f..0000000000000000000000000000000000000000
Binary files a/images/icons/icon-keypad-24.png and /dev/null differ
diff --git a/qml.qrc b/qml.qrc
index 5d20bbbedae736dd96a6b80461b8bc591b2d6610..d9e8085f772712c1c18f0f242e5b79c54fdf9ee0 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -102,5 +102,6 @@
         <file>src/commoncomponents/MaterialButton.qml</file>
         <file>src/mainview/components/RecordBox.qml</file>
         <file>src/commoncomponents/ElidedTextLabel.qml</file>
+        <file>src/mainview/components/SipInputPanel.qml</file>
     </qresource>
 </RCC>
diff --git a/ressources.qrc b/ressources.qrc
index f64f592a1d92eeb432ee264846f5155b7afaa636..357fe0bf761a5ed4f63333036d0ecc84221c1cd4 100644
--- a/ressources.qrc
+++ b/ressources.qrc
@@ -44,6 +44,7 @@
         <file>images/icons/ic_content_copy.svg</file>
         <file>images/icons/ic_delete_black_18dp_2x.png</file>
         <file>images/icons/ic_done_white_24dp.png</file>
+        <file>images/icons/ic_keypad.svg</file>
         <file>images/icons/open_in_full-24px.svg</file>
         <file>images/icons/close_fullscreen-24px.svg</file>
         <file>images/icons/ic_group_add_white_24dp.png</file>
@@ -97,9 +98,7 @@
         <file>images/icons/screen_share-24px.svg</file>
         <file>images/icons/round-add_a_photo-24px.svg</file>
         <file>images/icons/ic_mic_white_24dp.png</file>
-        <file>images/icons/icon-keypad-24.png</file>
         <file>images/icons/ic_play_white_24dp.png</file>
-        <file>images/icons/icon-keypad-24-2x.png</file>
         <file>images/icons/ic_voicemail_black_24dp_2x_.png</file>
         <file>images/icons/av_icons/delete-24px.svg</file>
         <file>images/icons/av_icons/fiber_manual_record-24px.svg</file>
diff --git a/src/calladapter.cpp b/src/calladapter.cpp
index efc74102d9d4fa29c575d19f81edfda98df0e4da..c4badc91d9379d96bda348fb51b26c90b1631886 100644
--- a/src/calladapter.cpp
+++ b/src/calladapter.cpp
@@ -27,7 +27,7 @@
 #include "globalsystemtray.h"
 #include "utils.h"
 
-CallAdapter::CallAdapter(QObject *parent)
+CallAdapter::CallAdapter(QObject* parent)
     : QmlAdapterBase(parent)
     , oneSecondTimer_(new QTimer(this))
 {}
@@ -78,7 +78,7 @@ CallAdapter::placeCall()
 }
 
 void
-CallAdapter::hangUpACall(const QString &accountId, const QString &convUid)
+CallAdapter::hangUpACall(const QString& accountId, const QString& convUid)
 {
     auto* convModel = LRCInstance::getCurrentConversationModel();
     const auto convInfo = convModel->getConversationForUID(convUid);
@@ -88,7 +88,7 @@ CallAdapter::hangUpACall(const QString &accountId, const QString &convUid)
 }
 
 void
-CallAdapter::refuseACall(const QString &accountId, const QString &convUid)
+CallAdapter::refuseACall(const QString& accountId, const QString& convUid)
 {
     auto* convModel = LRCInstance::getCurrentConversationModel();
     const auto convInfo = convModel->getConversationForUID(convUid);
@@ -98,20 +98,20 @@ CallAdapter::refuseACall(const QString &accountId, const QString &convUid)
 }
 
 void
-CallAdapter::acceptACall(const QString &accountId, const QString &convUid)
+CallAdapter::acceptACall(const QString& accountId, const QString& convUid)
 {
     emit incomingCallNeedToSetupMainView(accountId, convUid);
     auto* convModel = LRCInstance::getCurrentConversationModel();
     const auto convInfo = convModel->getConversationForUID(convUid);
     if (!convInfo.uid.isEmpty()) {
         LRCInstance::getAccountInfo(accountId).callModel->accept(convInfo.callId);
-        auto &accInfo = LRCInstance::getAccountInfo(convInfo.accountId);
+        auto& accInfo = LRCInstance::getAccountInfo(convInfo.accountId);
         accInfo.callModel->setCurrentCall(convInfo.callId);
     }
 }
 
 void
-CallAdapter::slotShowIncomingCallView(const QString &accountId, const conversation::Info &convInfo)
+CallAdapter::slotShowIncomingCallView(const QString& accountId, const conversation::Info& convInfo)
 {
     auto* callModel = LRCInstance::getCurrentCallModel();
 
@@ -119,17 +119,17 @@ CallAdapter::slotShowIncomingCallView(const QString &accountId, const conversati
         /*
          * Connection to close potential incoming call page when it is not current account.
          */
-        auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
+        auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
 
         QObject::disconnect(closeIncomingCallPageConnection_);
 
         closeIncomingCallPageConnection_
             = QObject::connect(accInfo.callModel.get(),
                                &lrc::api::NewCallModel::callStatusChanged,
-                               [this, accountId, uid = convInfo.uid](const QString &callId) {
-                                   auto &accInfo = LRCInstance::accountModel().getAccountInfo(
+                               [this, accountId, uid = convInfo.uid](const QString& callId) {
+                                   auto& accInfo = LRCInstance::accountModel().getAccountInfo(
                                        accountId);
-                                   auto &callModel = accInfo.callModel;
+                                   auto& callModel = accInfo.callModel;
                                    auto call = callModel->getCall(callId);
 
                                    switch (call.status) {
@@ -182,18 +182,18 @@ CallAdapter::slotShowIncomingCallView(const QString &accountId, const conversati
 }
 
 void
-CallAdapter::slotShowCallView(const QString &accountId, const lrc::api::conversation::Info &convInfo)
+CallAdapter::slotShowCallView(const QString& accountId, const lrc::api::conversation::Info& convInfo)
 {
     updateCall(convInfo.uid, accountId);
 }
 
 void
-CallAdapter::updateCall(const QString &convUid, const QString &accountId, bool forceCallOnly)
+CallAdapter::updateCall(const QString& convUid, const QString& accountId, bool forceCallOnly)
 {
     accountId_ = accountId.isEmpty() ? accountId_ : accountId;
     convUid_ = convUid.isEmpty() ? convUid_ : convUid;
 
-    auto *convModel = LRCInstance::getCurrentConversationModel();
+    auto* convModel = LRCInstance::getCurrentConversationModel();
     const auto convInfo = convModel->getConversationForUID(convUid_);
     if (convInfo.uid.isEmpty()) {
         return;
@@ -223,7 +223,7 @@ CallAdapter::updateCall(const QString &convUid, const QString &accountId, bool f
 bool
 CallAdapter::shouldShowPreview(bool force)
 {
-    bool shouldShowPreview{false};
+    bool shouldShowPreview {false};
     auto* convModel = LRCInstance::getCurrentConversationModel();
     const auto convInfo = convModel->getConversationForUID(convUid_);
     if (convInfo.uid.isEmpty()) {
@@ -245,11 +245,11 @@ CallAdapter::getConferencesInfos()
     const auto convInfo = convModel->getConversationForUID(convUid_);
     if (convInfo.uid.isEmpty())
         return map;
-    auto callId = convInfo.confId.isEmpty()? convInfo.callId : convInfo.confId;
+    auto callId = convInfo.confId.isEmpty() ? convInfo.callId : convInfo.confId;
     if (!callId.isEmpty()) {
         try {
             auto call = LRCInstance::getCurrentCallModel()->getCall(callId);
-            for (const auto& participant: call.participantsInfos) {
+            for (const auto& participant : call.participantsInfos) {
                 QJsonObject data;
                 data["x"] = participant["x"].toInt();
                 data["y"] = participant["y"].toInt();
@@ -257,31 +257,34 @@ CallAdapter::getConferencesInfos()
                 data["h"] = participant["h"].toInt();
                 data["active"] = participant["active"] == "true";
                 auto bestName = participant["uri"];
-                auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId_);
+                auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId_);
                 data["isLocal"] = false;
                 if (bestName == accInfo.profileInfo.uri) {
                     bestName = tr("me");
                     data["isLocal"] = true;
                 } else {
                     try {
-                        auto &contact = LRCInstance::getCurrentAccountInfo().contactModel->getContact(participant["uri"]);
+                        auto& contact = LRCInstance::getCurrentAccountInfo()
+                                            .contactModel->getContact(participant["uri"]);
                         bestName = Utils::bestNameForContact(contact);
-                    } catch (...) {}
+                    } catch (...) {
+                    }
                 }
                 data["bestName"] = bestName;
 
                 map.push_back(QVariant(data));
             }
             return map;
-        } catch (...) {}
+        } catch (...) {
+        }
     }
     return map;
 }
 
 void
-CallAdapter::connectCallModel(const QString &accountId)
+CallAdapter::connectCallModel(const QString& accountId)
 {
-    auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
+    auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
 
     QObject::disconnect(callStatusChangedConnection_);
     QObject::disconnect(onParticipantsChangedConnection_);
@@ -289,15 +292,15 @@ CallAdapter::connectCallModel(const QString &accountId)
     onParticipantsChangedConnection_ = QObject::connect(
         accInfo.callModel.get(),
         &lrc::api::NewCallModel::onParticipantsChanged,
-        [this, accountId](const QString &confId) {
-            auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
-            auto &callModel = accInfo.callModel;
+        [this, accountId](const QString& confId) {
+            auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
+            auto& callModel = accInfo.callModel;
             auto call = callModel->getCall(confId);
             const auto convInfo = LRCInstance::getConversationFromCallId(confId);
             if (!convInfo.uid.isEmpty()) {
                 // Convert to QML
                 QVariantList map;
-                for (const auto& participant: call.participantsInfos) {
+                for (const auto& participant : call.participantsInfos) {
                     QJsonObject data;
                     data["x"] = participant["x"].toInt();
                     data["y"] = participant["y"].toInt();
@@ -307,29 +310,31 @@ CallAdapter::connectCallModel(const QString &accountId)
                     data["active"] = participant["active"] == "true";
                     auto bestName = participant["uri"];
                     data["isLocal"] = false;
-                    auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId_);
+                    auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId_);
                     if (bestName == accInfo.profileInfo.uri) {
                         bestName = tr("me");
                         data["isLocal"] = true;
                     } else {
                         try {
-                            auto &contact = LRCInstance::getCurrentAccountInfo().contactModel->getContact(participant["uri"]);
+                            auto& contact = LRCInstance::getCurrentAccountInfo()
+                                                .contactModel->getContact(participant["uri"]);
                             bestName = Utils::bestNameForContact(contact);
-                        } catch (...) {}
+                        } catch (...) {
+                        }
                     }
                     data["bestName"] = bestName;
                     map.push_back(QVariant(data));
                 }
                 emit updateParticipantsInfos(map, accountId, confId);
             }
-    });
+        });
 
     callStatusChangedConnection_ = QObject::connect(
         accInfo.callModel.get(),
         &lrc::api::NewCallModel::callStatusChanged,
-        [this, accountId](const QString &callId) {
-            auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
-            auto &callModel = accInfo.callModel;
+        [this, accountId](const QString& callId) {
+            auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
+            auto& callModel = accInfo.callModel;
             const auto call = callModel->getCall(callId);
 
             /*
@@ -357,7 +362,7 @@ CallAdapter::connectCallModel(const QString &accountId)
                  * If it's a conference, change the smartlist index
                  * to the next remaining participant.
                  */
-                bool forceCallOnly{false};
+                bool forceCallOnly {false};
                 if (!convInfo.confId.isEmpty()) {
                     auto callList = LRCInstance::getAPI().getConferenceSubcalls(convInfo.confId);
                     if (callList.empty()) {
@@ -366,7 +371,7 @@ CallAdapter::connectCallModel(const QString &accountId)
                         callList.append(lastConferencee);
                         forceCallOnly = true;
                     }
-                    for (const auto &callId : callList) {
+                    for (const auto& callId : callList) {
                         if (!callModel->hasCall(callId)) {
                             continue;
                         }
@@ -408,17 +413,28 @@ CallAdapter::connectCallModel(const QString &accountId)
         });
 }
 
+void
+CallAdapter::sipInputPanelPlayDTMF(const QString& key)
+{
+    auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
+    if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
+        return;
+    }
+
+    LRCInstance::getCurrentCallModel()->playDTMF(callId, key);
+}
+
 /*
  * For Call Overlay
  */
 void
-CallAdapter::updateCallOverlay(const lrc::api::conversation::Info &convInfo)
+CallAdapter::updateCallOverlay(const lrc::api::conversation::Info& convInfo)
 {
     setTime(accountId_, convUid_);
     QObject::disconnect(oneSecondTimer_);
     QObject::connect(oneSecondTimer_, &QTimer::timeout, [this] { setTime(accountId_, convUid_); });
     oneSecondTimer_->start(20);
-    auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId_);
+    auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId_);
 
     auto call = LRCInstance::getCallInfoForConversation(convInfo);
     if (!call) {
@@ -450,13 +466,13 @@ CallAdapter::hangupCall(const QString& uri)
         auto callModel = LRCInstance::getAccountInfo(accountId_).callModel.get();
         if (callModel->hasCall(convInfo.callId)) {
             /*
-            * Store the last remaining participant of the conference,
-            * so we can switch the smartlist index after termination.
-            */
+             * Store the last remaining participant of the conference,
+             * so we can switch the smartlist index after termination.
+             */
             if (!convInfo.confId.isEmpty()) {
                 auto callList = LRCInstance::getAPI().getConferenceSubcalls(convInfo.confId);
                 if (callList.size() == 2) {
-                    for (const auto &cId : callList) {
+                    for (const auto& cId : callList) {
                         if (cId != convInfo.callId) {
                             LRCInstance::instance().pushLastConferencee(convInfo.confId, cId);
                         }
@@ -487,21 +503,23 @@ CallAdapter::maximizeParticipant(const QString& uri, bool isActive)
     try {
         const auto call = callModel->getCall(confId);
         switch (call.layout) {
-            case lrc::api::call::Layout::GRID:
-                callModel->setActiveParticipant(confId, callId);
-                callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL);
-                break;
-            case lrc::api::call::Layout::ONE_WITH_SMALL:
-                callModel->setActiveParticipant(confId, callId);
-                callModel->setConferenceLayout(confId,
-                    isActive? lrc::api::call::Layout::ONE : lrc::api::call::Layout::ONE_WITH_SMALL);
-                break;
-            case lrc::api::call::Layout::ONE:
-                callModel->setActiveParticipant(confId, callId);
-                callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID);
-                break;
+        case lrc::api::call::Layout::GRID:
+            callModel->setActiveParticipant(confId, callId);
+            callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL);
+            break;
+        case lrc::api::call::Layout::ONE_WITH_SMALL:
+            callModel->setActiveParticipant(confId, callId);
+            callModel->setConferenceLayout(confId,
+                                           isActive ? lrc::api::call::Layout::ONE
+                                                    : lrc::api::call::Layout::ONE_WITH_SMALL);
+            break;
+        case lrc::api::call::Layout::ONE:
+            callModel->setActiveParticipant(confId, callId);
+            callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID);
+            break;
         };
-    } catch (...) {}
+    } catch (...) {
+    }
 }
 
 void
@@ -514,16 +532,17 @@ CallAdapter::minimizeParticipant()
     try {
         auto call = callModel->getCall(confId);
         switch (call.layout) {
-            case lrc::api::call::Layout::GRID:
-                break;
-            case lrc::api::call::Layout::ONE_WITH_SMALL:
-                callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID);
-                break;
-            case lrc::api::call::Layout::ONE:
-                callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL);
-                break;
+        case lrc::api::call::Layout::GRID:
+            break;
+        case lrc::api::call::Layout::ONE_WITH_SMALL:
+            callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID);
+            break;
+        case lrc::api::call::Layout::ONE:
+            callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL);
+            break;
         };
-    } catch (...) {}
+    } catch (...) {
+    }
 }
 
 void
@@ -544,11 +563,11 @@ CallAdapter::hangUpThisCall()
 bool
 CallAdapter::isRecordingThisCall()
 {
-    auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId_);
-    auto &convModel = accInfo.conversationModel;
+    auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId_);
+    auto& convModel = accInfo.conversationModel;
     const auto convInfo = convModel->getConversationForUID(convUid_);
     return accInfo.callModel->isRecording(convInfo.confId)
-    || accInfo.callModel->isRecording(convInfo.callId);
+           || accInfo.callModel->isRecording(convInfo.callId);
 }
 
 bool
@@ -565,7 +584,8 @@ CallAdapter::isCurrentMaster() const
                 auto call = callModel->getCall(convInfo.callId);
                 return call.participantsInfos.size() == 0;
             }
-        } catch (...) {}
+        } catch (...) {
+        }
     }
     return true;
 }
@@ -580,7 +600,8 @@ CallAdapter::getCurrentLayoutType() const
         try {
             auto call = callModel->getCall(convInfo.confId);
             return Utils::toUnderlyingValue(call.layout);
-        } catch (...) {}
+        } catch (...) {
+        }
     }
     return -1;
 }
@@ -640,7 +661,7 @@ CallAdapter::videoPauseThisCallToggle()
 }
 
 void
-CallAdapter::setTime(const QString &accountId, const QString &convUid)
+CallAdapter::setTime(const QString& accountId, const QString& convUid)
 {
     const auto callId = LRCInstance::getCallIdForConversationUid(convUid, accountId);
     if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
@@ -652,4 +673,4 @@ CallAdapter::setTime(const QString &accountId, const QString &convUid)
         auto timeString = LRCInstance::getCurrentCallModel()->getFormattedCallDuration(callId);
         emit updateTimeText(timeString);
     }
-}
+}
\ No newline at end of file
diff --git a/src/calladapter.h b/src/calladapter.h
index ce439731b9e4b296320a073b5d7574b50d0f789d..fbf5ffc4d0be44620218c80f010a63ffdeb75f89 100644
--- a/src/calladapter.h
+++ b/src/calladapter.h
@@ -31,7 +31,7 @@ class CallAdapter : public QmlAdapterBase
     Q_OBJECT
 
 public:
-    explicit CallAdapter(QObject *parent = nullptr);
+    explicit CallAdapter(QObject* parent = nullptr);
     ~CallAdapter();
 
     /*
@@ -41,11 +41,12 @@ public:
 
     Q_INVOKABLE void placeAudioOnlyCall();
     Q_INVOKABLE void placeCall();
-    Q_INVOKABLE void hangUpACall(const QString &accountId, const QString &convUid);
-    Q_INVOKABLE void refuseACall(const QString &accountId, const QString &convUid);
-    Q_INVOKABLE void acceptACall(const QString &accountId, const QString &convUid);
+    Q_INVOKABLE void hangUpACall(const QString& accountId, const QString& convUid);
+    Q_INVOKABLE void refuseACall(const QString& accountId, const QString& convUid);
+    Q_INVOKABLE void acceptACall(const QString& accountId, const QString& convUid);
 
-    Q_INVOKABLE void connectCallModel(const QString &accountId);
+    Q_INVOKABLE void connectCallModel(const QString& accountId);
+    Q_INVOKABLE void sipInputPanelPlayDTMF(const QString& key);
 
     /*
      * For Call Overlay
@@ -55,7 +56,7 @@ public:
     Q_INVOKABLE void minimizeParticipant();
     Q_INVOKABLE void hangUpThisCall();
     Q_INVOKABLE bool isCurrentMaster() const;
-    Q_INVOKABLE int  getCurrentLayoutType() const;
+    Q_INVOKABLE int getCurrentLayoutType() const;
     Q_INVOKABLE void holdThisCallToggle();
     Q_INVOKABLE void muteThisCallToggle();
     Q_INVOKABLE void recordThisCallToggle();
@@ -64,24 +65,26 @@ public:
     Q_INVOKABLE QVariantList getConferencesInfos();
 
 signals:
-    void showOutgoingCallPage(const QString &accountId, const QString &convUid);
-    void showIncomingCallPage(const QString &accountId, const QString &convUid);
-    void showAudioCallPage(const QString &accountId, const QString &convUid);
-    void showVideoCallPage(const QString &accountId, const QString &convUid, const QString &callId);
-    void showCallStack(const QString &accountId, const QString &convUid, bool forceReset = false);
-    void closeCallStack(const QString &accountId, const QString &convUid);
-    void closePotentialIncomingCallPageWindow(const QString &accountId, const QString &convUid);
-    void callStatusChanged(const QString &status, const QString &accountId, const QString &convUid);
+    void showOutgoingCallPage(const QString& accountId, const QString& convUid);
+    void showIncomingCallPage(const QString& accountId, const QString& convUid);
+    void showAudioCallPage(const QString& accountId, const QString& convUid);
+    void showVideoCallPage(const QString& accountId, const QString& convUid, const QString& callId);
+    void showCallStack(const QString& accountId, const QString& convUid, bool forceReset = false);
+    void closeCallStack(const QString& accountId, const QString& convUid);
+    void closePotentialIncomingCallPageWindow(const QString& accountId, const QString& convUid);
+    void callStatusChanged(const QString& status, const QString& accountId, const QString& convUid);
     void updateConversationSmartList();
-    void updateParticipantsInfos(const QVariantList& infos, const QString &accountId, const QString &callId);
+    void updateParticipantsInfos(const QVariantList& infos,
+                                 const QString& accountId,
+                                 const QString& callId);
 
-    void incomingCallNeedToSetupMainView(const QString &accountId, const QString &convUid);
+    void incomingCallNeedToSetupMainView(const QString& accountId, const QString& convUid);
     void previewVisibilityNeedToChange(bool visible);
 
     /*
      * For Call Overlay
      */
-    void updateTimeText(const QString &time);
+    void updateTimeText(const QString& time);
     void showOnHoldLabel(bool isPaused);
     void updateOverlay(bool isPaused,
                        bool isAudioOnly,
@@ -90,17 +93,17 @@ signals:
                        bool isRecording,
                        bool isSIP,
                        bool isConferenceCall,
-                       const QString &bestName);
+                       const QString& bestName);
 
 public slots:
-    void slotShowIncomingCallView(const QString &accountId,
-                                  const lrc::api::conversation::Info &convInfo);
-    void slotShowCallView(const QString &accountId, const lrc::api::conversation::Info &convInfo);
+    void slotShowIncomingCallView(const QString& accountId,
+                                  const lrc::api::conversation::Info& convInfo);
+    void slotShowCallView(const QString& accountId, const lrc::api::conversation::Info& convInfo);
     void slotAccountChanged();
 
 private:
-    void updateCall(const QString &convUid = {},
-                    const QString &accountId = {},
+    void updateCall(const QString& convUid = {},
+                    const QString& accountId = {},
                     bool forceCallOnly = false);
     bool shouldShowPreview(bool force);
 
@@ -117,7 +120,7 @@ private:
     /*
      * For Call Overlay
      */
-    void updateCallOverlay(const lrc::api::conversation::Info &convInfo);
-    void setTime(const QString &accountId, const QString &convUid);
-    QTimer *oneSecondTimer_;
+    void updateCallOverlay(const lrc::api::conversation::Info& convInfo);
+    void setTime(const QString& accountId, const QString& convUid);
+    QTimer* oneSecondTimer_;
 };
diff --git a/src/commoncomponents/HoverableButton.qml b/src/commoncomponents/HoverableButton.qml
index 4e96677d29d3773fccbc58e9f6f8ed04f7cc1608..0668487b9e55effe96ab7b86e9dc45f2d2b20747 100644
--- a/src/commoncomponents/HoverableButton.qml
+++ b/src/commoncomponents/HoverableButton.qml
@@ -28,6 +28,8 @@ import QtGraphicalEffects 1.15
  * 2. Radius control (rounded)
  * 3. Text content or image content
  * 4. Can use OnClicked slot to implement some click logic
+ *
+ * Note: if use text property directly, buttonTextColor will not work.
  */
 Button {
     id: hoverableButton
@@ -39,6 +41,9 @@ Button {
     property int buttonImageHeight: hoverableButtonBackground.height - 10
     property int buttonImageWidth: hoverableButtonBackground.width - 10
 
+    property string buttonText: ""
+    property string buttonTextColor: "black"
+
     property string backgroundColor: JamiTheme.releaseColor
     property string onPressColor: JamiTheme.pressColor
     property string onReleaseColor: JamiTheme.releaseColor
@@ -58,6 +63,8 @@ Button {
 
     hoverEnabled: true
 
+    text: "<font color=" + "'" + buttonTextColor + "'>" + buttonText + "</font>"
+
     ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
     ToolTip.visible: hovered && (toolTipText.length > 0)
     ToolTip.text: toolTipText
@@ -79,7 +86,12 @@ Button {
             mipmap: true
             asynchronous: true
 
-            source: hoverableButton.checked && checkedImage? checkedImage : baseImage
+            source: {
+                if (checkable && checkedImage)
+                    return hoverableButton.checked ? checkedImage : baseImage
+                else
+                    return ""
+            }
 
             layer {
                 enabled: true
diff --git a/src/constant/JamiTheme.qml b/src/constant/JamiTheme.qml
index a29330de120609ce43783d2b102f212c8fe92e05..d9086c69ce843ff5dcb1e95f5d9916ddfee5aece 100644
--- a/src/constant/JamiTheme.qml
+++ b/src/constant/JamiTheme.qml
@@ -68,6 +68,9 @@ Item {
 
     property string draftRed: "#cf5300"
 
+    property string sipInputButtonBackgroundColor: "#336699"
+    property string sipInputButtonHoverColor: "#4477aa"
+    property string sipInputButtonPressColor: "#5588bb"
 
     /*
      * Font.
diff --git a/src/mainview/components/CallOverlay.qml b/src/mainview/components/CallOverlay.qml
index 75774d34f1aa8aa61465ba1fc0ff6eae44070346..1f2ed4edea58c9b53fa4eaea90b3effcb17bafe1 100644
--- a/src/mainview/components/CallOverlay.qml
+++ b/src/mainview/components/CallOverlay.qml
@@ -110,6 +110,12 @@ Rectangle {
 
     anchors.fill: parent
 
+    SipInputPanel {
+        id: sipInputPanel
+
+        x: callOverlayRect.width / 2 - sipInputPanel.width / 2
+        y: callOverlayRect.height / 2 - sipInputPanel.height / 2
+    }
 
     /*
      * Timer to decide when overlay fade out.
diff --git a/src/mainview/components/CallOverlayButtonGroup.qml b/src/mainview/components/CallOverlayButtonGroup.qml
index c360a7cdaae3edab6be8508ac45081d580b02d27..107ab9e25b1365cfa7c3f233729d745316899712 100644
--- a/src/mainview/components/CallOverlayButtonGroup.qml
+++ b/src/mainview/components/CallOverlayButtonGroup.qml
@@ -50,8 +50,6 @@ Rectangle {
         root.isSip = isSIP
         noVideoButton.visible = !isAudioOnly
         addToConferenceButton.visible = !isSIP && isMaster
-        transferCallButton.visible = isSIP
-        sipInputPanelButton.visible = isSIP
 
         noMicButton.checked = isAudioMuted
         noVideoButton.checked = isVideoMuted
diff --git a/src/mainview/components/CallViewContextMenu.qml b/src/mainview/components/CallViewContextMenu.qml
index 23da3fd700184a619072fef6b35d1437f70e9a46..2deeeaac2a440f582891946b01de78226b1aa831 100644
--- a/src/mainview/components/CallViewContextMenu.qml
+++ b/src/mainview/components/CallViewContextMenu.qml
@@ -112,6 +112,23 @@ Menu {
         }
     }
 
+    GeneralMenuItem {
+        id: showSipInputPanelButton
+
+        visible: isSIP
+        height: isSIP? undefined : 0
+
+        itemName: qsTr("Sip Input Panel")
+        iconSource: "qrc:/images/icons/ic_keypad.svg"
+        leftBorderWidth: commonBorderWidth
+        rightBorderWidth: commonBorderWidth
+
+        onClicked: {
+            root.close()
+            sipInputPanel.open()
+        }
+    }
+
     GeneralMenuItem {
         id: transferCallButton
 
diff --git a/src/mainview/components/SipInputPanel.qml b/src/mainview/components/SipInputPanel.qml
new file mode 100644
index 0000000000000000000000000000000000000000..8d547a26a4356b8d8ef52a4db191868f688ebee0
--- /dev/null
+++ b/src/mainview/components/SipInputPanel.qml
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 by Savoir-faire Linux
+ * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
+ *
+ * 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; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.14
+import QtQuick.Controls 2.14
+import QtQuick.Layouts 1.14
+import QtQuick.Controls.Universal 2.12
+import net.jami.Models 1.0
+
+import "../../commoncomponents"
+
+/*
+ * SipInputPanel is a key pad that is designed to be
+ * used in sip calls.
+ */
+Popup {
+    id: sipInputPanelPopUp
+
+    /*
+     * Space between sipInputPanelRect and grid layout
+     */
+    property int sipPanelPadding: 20
+
+    contentWidth: sipInputPanelRectGridLayout.implicitWidth + 20
+    contentHeight: sipInputPanelRectGridLayout.implicitHeight + 20
+
+    padding: 0
+
+    modal: true
+
+    contentItem: Rectangle {
+        id: sipInputPanelRect
+
+        radius: 10
+
+        GridLayout {
+            id: sipInputPanelRectGridLayout
+
+            anchors.centerIn: parent
+
+            columns: 4
+
+            Repeater {
+                id: sipInputPanelRectGridLayoutRepeater
+                model: ["1", "2", "3", "A", "4", "5", "6", "B", "7",
+                        "8", "9", "C", "*", "0", "#", "D"]
+
+                HoverableButton {
+                    id: sipInputPanelButton
+
+                    Layout.preferredWidth: 30
+                    Layout.preferredHeight: 30
+
+                    radius: 30
+                    buttonText: modelData
+                    buttonTextColor: "white"
+                    checkable: false
+                    backgroundColor: JamiTheme.sipInputButtonBackgroundColor
+                    onEnterColor: JamiTheme.sipInputButtonHoverColor
+                    onExitColor: JamiTheme.sipInputButtonBackgroundColor
+                    onPressColor: JamiTheme.sipInputButtonPressColor
+                    onReleaseColor: JamiTheme.sipInputButtonHoverColor
+
+                    toolTipText: modelData
+
+                    onClicked: {
+                        CallAdapter.sipInputPanelPlayDTMF(modelData)
+                    }
+                }
+            }
+        }
+    }
+
+    background: Rectangle {
+        color: "transparent"
+    }
+}