Add a Windows Hello / Touch ID system unlock.

This commit is contained in:
John Preston 2024-06-25 18:00:28 +04:00
parent 7e704d9529
commit 1988435cdf
17 changed files with 225 additions and 6 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -683,6 +683,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_privacy_premium_link" = "Telegram Premium";
"lng_settings_passcode_disable" = "Disable Passcode";
"lng_settings_passcode_disable_sure" = "Are you sure you want to disable passcode?";
"lng_settings_use_winhello" = "Unlock with Windows Hello";
"lng_settings_use_winhello_about" = "You need to enter your passcode once before unlocking Telegram with Windows Hello.";
"lng_settings_use_touchid" = "Unlock with Touch ID";
"lng_settings_use_touchid_about" = "You need to enter your passcode once before unlocking Telegram with Touch ID.";
"lng_settings_password_disable" = "Disable Cloud Password";
"lng_settings_password_abort" = "Abort two-step verification setup";
"lng_settings_about_bio" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco";
@ -992,6 +996,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passcode_ph" = "Your passcode";
"lng_passcode_submit" = "Submit";
"lng_passcode_logout" = "Log out";
"lng_passcode_winhello" = "You need to enter your passcode\nbefore you can use Windows Hello.";
"lng_passcode_touchid" = "You need to enter your passcode\nbefore you can use Touch ID.";
"lng_passcode_winhello_unlock" = "Telegram wants to unlock with Windows Hello.";
"lng_passcode_touchid_unlock" = "unlock";
"lng_passcode_create_button" = "Save Passcode";
"lng_passcode_check_button" = "Submit";
"lng_passcode_change_button" = "Save Passcode";

View file

@ -290,6 +290,28 @@ passcodeTextLine: 28px;
passcodeLittleSkip: 5px;
passcodeAboutSkip: 7px;
passcodeSkip: 23px;
passcodeSystemUnlock: IconButton(defaultIconButton) {
width: 32px;
height: 32px;
rippleAreaSize: 24px;
rippleAreaPosition: point(4px, 4px);
ripple: RippleAnimation(defaultRippleAnimation) {
color: lightButtonBgOver;
}
}
passcodeSystemWinHello: IconButton(passcodeSystemUnlock) {
icon: icon{{ "menu/passcode_winhello", lightButtonFg }};
iconOver: icon{{ "menu/passcode_winhello", lightButtonFg }};
}
passcodeSystemTouchID: IconButton(passcodeSystemUnlock) {
icon: icon{{ "menu/passcode_finger", lightButtonFg }};
iconOver: icon{{ "menu/passcode_finger", lightButtonFg }};
}
passcodeSystemUnlockLater: FlatLabel(defaultFlatLabel) {
align: align(top);
textFg: windowSubTextFg;
}
passcodeSystemUnlockSkip: 12px;
newGroupAboutFg: windowSubTextFg;
newGroupPadding: margins(4px, 6px, 4px, 3px);

View file

@ -220,7 +220,7 @@ QByteArray Settings::serialize() const {
+ Serialize::bytearraySize(ivPosition)
+ Serialize::stringSize(noWarningExtensions)
+ Serialize::stringSize(_customFontFamily)
+ sizeof(qint32);
+ sizeof(qint32) * 2;
auto result = QByteArray();
result.reserve(size);
@ -371,7 +371,8 @@ QByteArray Settings::serialize() const {
<< qint32(std::clamp(
qRound(_dialogsNoChatWidthRatio.current() * 1000000),
0,
1000000));
1000000))
<< qint32(_systemUnlockEnabled ? 1 : 0);
}
Ensures(result.size() == size);
@ -491,6 +492,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 ttlVoiceClickTooltipHidden = _ttlVoiceClickTooltipHidden.current() ? 1 : 0;
QByteArray ivPosition;
QString customFontFamily = _customFontFamily;
qint32 systemUnlockEnabled = _systemUnlockEnabled ? 1 : 0;
stream >> themesAccentColors;
if (!stream.atEnd()) {
@ -788,6 +790,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
0.,
1.);
}
if (!stream.atEnd()) {
stream >> systemUnlockEnabled;
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@ -995,6 +1000,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_ivPosition = Deserialize(ivPosition);
}
_customFontFamily = customFontFamily;
_systemUnlockEnabled = (systemUnlockEnabled == 1);
}
QString Settings::getSoundPath(const QString &key) const {

View file

@ -884,6 +884,13 @@ public:
_customFontFamily = value;
}
[[nodiscard]] bool systemUnlockEnabled() const {
return _systemUnlockEnabled;
}
void setSystemUnlockEnabled(bool enabled) {
_systemUnlockEnabled = enabled;
}
[[nodiscard]] static bool ThirdColumnByDefault();
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
@ -1014,6 +1021,7 @@ private:
rpl::variable<bool> _ttlVoiceClickTooltipHidden = false;
WindowPosition _ivPosition;
QString _customFontFamily;
bool _systemUnlockEnabled = false;
bool _tabbedReplacedWithInfo = false; // per-window
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window

View file

@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_local_passcode.h"
#include "base/platform/base_platform_last_input.h"
#include "base/platform/base_platform_info.h"
#include "base/system_unlock.h"
#include "boxes/auto_lock_box.h"
#include "core/application.h"
#include "core/core_settings.h"
@ -23,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/fields/password_input.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
@ -199,11 +202,11 @@ void LocalPasscodeEnter::setupContent() {
content,
object_ptr<Ui::RoundButton>(
content,
isCreate
(isCreate
? tr::lng_passcode_create_button()
: isCheck
? tr::lng_passcode_check_button()
: tr::lng_passcode_change_button(),
: tr::lng_passcode_change_button()),
st::changePhoneButton)),
st::settingLocalPasscodeButtonPadding)->entity();
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
@ -239,6 +242,8 @@ void LocalPasscodeEnter::setupContent() {
}
SetPasscode(_controller, newText);
if (isCreate) {
Core::App().settings().setSystemUnlockEnabled(true);
Core::App().saveSettingsDelayed();
_showOther.fire(LocalPasscodeManageId());
} else if (isChange) {
_showBack.fire({});
@ -506,6 +511,46 @@ void LocalPasscodeManage::setupContent() {
divider->skipEdge(Qt::BottomEdge, shown);
}, divider->lifetime());
const auto systemUnlockWrap = content->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
content,
object_ptr<Ui::VerticalLayout>(content))
)->setDuration(0);
const auto systemUnlockContent = systemUnlockWrap->entity();
Ui::AddSkip(systemUnlockContent);
AddButtonWithIcon(
systemUnlockContent,
(Platform::IsWindows()
? tr::lng_settings_use_winhello()
: tr::lng_settings_use_touchid()),
st::settingsButton,
{ Platform::IsWindows()
? &st::menuIconWinHello
: &st::menuIconTouchID }
)->toggleOn(
rpl::single(Core::App().settings().systemUnlockEnabled())
)->toggledChanges(
) | rpl::filter([=](bool value) {
return value != Core::App().settings().systemUnlockEnabled();
}) | rpl::start_with_next([=](bool value) {
Core::App().settings().setSystemUnlockEnabled(value);
Core::App().saveSettingsDelayed();
}, systemUnlockContent->lifetime());
Ui::AddSkip(systemUnlockContent);
Ui::AddDividerText(
systemUnlockContent,
(Platform::IsWindows()
? tr::lng_settings_use_winhello_about()
: tr::lng_settings_use_touchid_about()));
using namespace rpl::mappers;
systemUnlockWrap->toggleOn(base::SystemUnlockStatus(
) | rpl::map(_1 == base::SystemUnlockAvailability::Available));
Ui::ResizeFitChild(this, content);
}

View file

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_premium.h" // Settings::ShowPremium.
#include "settings/settings_privacy_controllers.h"
#include "settings/settings_websites.h"
#include "base/system_unlock.h"
#include "base/timer_rpl.h"
#include "boxes/passcode_box.h"
#include "boxes/sessions_box.h"
@ -1079,6 +1080,8 @@ PrivacySecurity::PrivacySecurity(
not_null<Window::SessionController*> controller)
: Section(parent) {
setupContent(controller);
[[maybe_unused]] auto preload = base::SystemUnlockStatus();
}
rpl::producer<QString> PrivacySecurity::title() {

View file

@ -155,6 +155,8 @@ menuIconTagRename: icon{{ "menu/tag_rename", menuIconColor }};
menuIconGroupsHide: icon {{ "menu/hide_members", menuIconColor }};
menuIconFont: icon {{ "menu/fonts", menuIconColor }};
menuIconFactcheck: icon {{ "menu/factcheck", menuIconColor }};
menuIconWinHello: icon {{ "menu/passcode_winhello", menuIconColor }};
menuIconTouchID: icon {{ "menu/passcode_finger", menuIconColor }};
menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }};
menuIconTTLAnyTextPosition: point(11px, 22px);

View file

@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "window/window_lock_widgets.h"
#include "base/platform/base_platform_info.h"
#include "base/call_delayed.h"
#include "base/system_unlock.h"
#include "lang/lang_keys.h"
#include "storage/storage_domain.h"
#include "mainwindow.h"
@ -27,6 +30,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h"
namespace Window {
namespace {
constexpr auto kSystemUnlockDelay = crl::time(1000);
} // namespace
LockWidget::LockWidget(QWidget *parent, not_null<Controller*> window)
: RpWidget(parent)
@ -99,6 +107,108 @@ PasscodeLockWidget::PasscodeLockWidget(
_logout->setClickedCallback([=] {
window->showLogoutConfirmation();
});
using namespace rpl::mappers;
if (Core::App().settings().systemUnlockEnabled()) {
_systemUnlockAvailable = base::SystemUnlockStatus()
| rpl::map(_1 == base::SystemUnlockAvailability::Available);
if (Core::App().domain().started()) {
_systemUnlockAllowed = _systemUnlockAvailable.value();
setupSystemUnlock();
} else {
setupSystemUnlockInfo();
}
}
}
void PasscodeLockWidget::setupSystemUnlockInfo() {
const auto info = Ui::CreateChild<Ui::FlatLabel>(
this,
(Platform::IsWindows()
? tr::lng_passcode_winhello()
: tr::lng_passcode_touchid()),
st::passcodeSystemUnlockLater);
_logout->geometryValue(
) | rpl::start_with_next([=](QRect logout) {
info->resizeToWidth(width()
- st::boxRowPadding.left()
- st::boxRowPadding.right());
info->moveToLeft(
st::boxRowPadding.left(),
logout.y() + logout.height() + st::passcodeSystemUnlockSkip);
}, info->lifetime());
info->showOn(_systemUnlockAvailable.value());
}
void PasscodeLockWidget::setupSystemUnlock() {
windowActiveValue() | rpl::skip(1) | rpl::filter([=](bool active) {
return active
&& !_systemUnlockSuggested
&& !_systemUnlockCooldown.isActive();
}) | rpl::start_with_next([=](bool) {
[[maybe_unused]] auto refresh = base::SystemUnlockStatus();
suggestSystemUnlock();
}, lifetime());
const auto button = Ui::CreateChild<Ui::IconButton>(
_passcode.data(),
(Platform::IsWindows()
? st::passcodeSystemWinHello
: st::passcodeSystemTouchID));
button->showOn(_systemUnlockAllowed.value());
_passcode->sizeValue() | rpl::start_with_next([=](QSize size) {
button->moveToRight(0, size.height() - button->height());
}, button->lifetime());
button->setClickedCallback([=] {
const auto delay = st::passcodeSystemUnlock.ripple.hideDuration;
base::call_delayed(delay, this, [=] {
suggestSystemUnlock();
});
});
}
void PasscodeLockWidget::suggestSystemUnlock() {
InvokeQueued(this, [=] {
if (_systemUnlockSuggested) {
return;
}
_systemUnlockCooldown.cancel();
using namespace base;
_systemUnlockAllowed.value(
) | rpl::filter(
rpl::mappers::_1
) | rpl::take(1) | rpl::start_with_next([=] {
const auto weak = Ui::MakeWeak(this);
const auto done = [weak](SystemUnlockResult result) {
crl::on_main([=] {
if (const auto strong = weak.data()) {
strong->systemUnlockDone(result);
}
});
};
SuggestSystemUnlock(
this,
(::Platform::IsWindows()
? tr::lng_passcode_winhello_unlock(tr::now)
: tr::lng_passcode_touchid_unlock(tr::now)),
done);
}, _systemUnlockSuggested);
});
}
void PasscodeLockWidget::systemUnlockDone(base::SystemUnlockResult result) {
if (result == base::SystemUnlockResult::Success) {
Core::App().unlockPasscode();
return;
}
_systemUnlockCooldown.callOnce(kSystemUnlockDelay);
_systemUnlockSuggested.destroy();
if (result == base::SystemUnlockResult::FloodError) {
_error = tr::lng_flood_error(tr::now);
_passcode->setFocusFast();
update();
}
}
void PasscodeLockWidget::paintContent(QPainter &p) {

View file

@ -11,6 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "ui/layers/box_content.h"
#include "base/bytes.h"
#include "base/timer.h"
namespace base {
enum class SystemUnlockResult;
} // namespace base
namespace Ui {
class PasswordInput;
@ -61,15 +66,25 @@ protected:
private:
void paintContent(QPainter &p) override;
void setupSystemUnlockInfo();
void setupSystemUnlock();
void suggestSystemUnlock();
void systemUnlockDone(base::SystemUnlockResult result);
void changed();
void submit();
void error();
rpl::variable<bool> _systemUnlockAvailable = false;
rpl::variable<bool> _systemUnlockAllowed = false;
object_ptr<Ui::PasswordInput> _passcode;
object_ptr<Ui::RoundButton> _submit;
object_ptr<Ui::LinkButton> _logout;
QString _error;
rpl::lifetime _systemUnlockSuggested;
base::Timer _systemUnlockCooldown;
};
struct TermsLock {

@ -1 +1 @@
Subproject commit 1be2a262a6524d6dec3b616c2e9fd2ce42c9e61a
Subproject commit b512eead302cb7b509869778348d60fef64bc19b

2
cmake

@ -1 +1 @@
Subproject commit b92244f0c21f157600484498c33a3566087526dd
Subproject commit 104406258fa89f007aad69348cef9b433f128fc2