├── .flake8 ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── .gitignore ├── .gitmodules ├── .isort.cfg ├── .mypy.ini ├── COPYING ├── COPYING.LESSER ├── README.md ├── autoreload.py ├── docs ├── CHANGELOG.md ├── CONFIG.md ├── CONTRIBUTING.md ├── INSTALL.md ├── PCN.md ├── THEMING.md └── screenshots │ ├── 01-chat.png │ ├── 02-sign-in.png │ ├── 03-account-settings.png │ ├── 04-create-room.png │ ├── 05-main-pane-small.png │ ├── 06-chat-small.png │ └── 07-room-pane-small.png ├── mirage.pro ├── packaging ├── appimage │ ├── AppRun.sh │ ├── README.md │ └── build.sh ├── flatpak │ ├── README.md │ ├── collector.py │ ├── generate-flatpak-script.sh │ ├── mirage.flatpak.base.yaml │ ├── mirage.flatpak.yaml │ └── requirements.flatpak.txt ├── mirage.appdata.xml ├── mirage.desktop ├── mirage.png └── update-appdata-releases.py ├── requirements-dev.txt ├── requirements.txt └── src ├── backend ├── __init__.py ├── backend.py ├── color.py ├── errors.py ├── html_markdown.py ├── matrix_client.py ├── media_cache.py ├── models │ ├── __init__.py │ ├── filters.py │ ├── items.py │ ├── model.py │ ├── model_item.py │ ├── model_store.py │ ├── proxy.py │ └── special_models.py ├── nio_callbacks.py ├── pcn │ ├── __init__.py │ ├── globals_dict.py │ ├── property.py │ └── section.py ├── presence.py ├── pyotherside_events.py ├── qml_bridge.py ├── sso_server.py ├── theme_parser.py ├── user_files.py └── utils.py ├── clipboard.h ├── clipboard_image_provider.h ├── config └── settings.py ├── fonts ├── hack │ ├── LICENSES.md │ ├── bold-italic.ttf │ ├── bold.ttf │ ├── italic.ttf │ └── regular.ttf └── roboto │ ├── LICENSE.txt │ ├── bold-italic.ttf │ ├── bold.ttf │ ├── italic.ttf │ └── regular.ttf ├── gui ├── ArgumentParser.qml ├── Base │ ├── AutoDirectionLayout.qml │ ├── Buttons │ │ ├── ApplyButton.qml │ │ ├── CancelButton.qml │ │ ├── FieldCopyButton.qml │ │ ├── FieldHelpButton.qml │ │ ├── GroupButton.qml │ │ ├── MiddleButton.qml │ │ ├── NegativeButton.qml │ │ └── PositiveButton.qml │ ├── Class.qml │ ├── DelegateTransitionFixer.qml │ ├── HAvatar.qml │ ├── HBottomFocusLine.qml │ ├── HBox.qml │ ├── HBusyIndicator.qml │ ├── HButton.qml │ ├── HButtonBackground.qml │ ├── HButtonContent.qml │ ├── HCheckBox.qml │ ├── HCircleProgressBar.qml │ ├── HColorAnimation.qml │ ├── HColumnLayout.qml │ ├── HColumnPage.qml │ ├── HComboBox.qml │ ├── HDrawer.qml │ ├── HDrawerSwipeHandler.qml │ ├── HFlickable.qml │ ├── HFlickableColumnPage.qml │ ├── HFlow.qml │ ├── HGridLayout.qml │ ├── HGridView.qml │ ├── HIcon.qml │ ├── HImage.qml │ ├── HKineticScrollingDisabler.qml │ ├── HLabel.qml │ ├── HLabeledItem.qml │ ├── HListView.qml │ ├── HLoader.qml │ ├── HMenu.qml │ ├── HMenuItem.qml │ ├── HMenuItemPopupSpawner.qml │ ├── HMenuSeparator.qml │ ├── HMxcImage.qml │ ├── HNoticePage.qml │ ├── HNumberAnimation.qml │ ├── HPage.qml │ ├── HPauseAnimation.qml │ ├── HPopup.qml │ ├── HProgressBar.qml │ ├── HQtObject.qml │ ├── HRadioButton.qml │ ├── HRectangleBottomBorder.qml │ ├── HRepeater.qml │ ├── HRoomAvatar.qml │ ├── HRowLayout.qml │ ├── HScrollBar.qml │ ├── HScrollView.qml │ ├── HSelectableLabel.qml │ ├── HShortcut.qml │ ├── HSlider.qml │ ├── HSpacer.qml │ ├── HSpinBox.qml │ ├── HStackView.qml │ ├── HSwipeView.qml │ ├── HTabBar.qml │ ├── HTabButton.qml │ ├── HTabbedBox.qml │ ├── HTextArea.qml │ ├── HTextContextMenu.qml │ ├── HTextField.qml │ ├── HTile │ │ ├── ContentRow.qml │ │ ├── HTile.qml │ │ ├── SubtitleLabel.qml │ │ ├── TitleLabel.qml │ │ └── TitleRightInfoLabel.qml │ ├── HToolTip.qml │ ├── HUserAvatar.qml │ ├── MediaPlayer │ │ ├── AudioPlayer.qml │ │ ├── OSD.qml │ │ ├── OSDButton.qml │ │ ├── OSDLabel.qml │ │ └── VideoPlayer.qml │ ├── MultiviewPane.qml │ ├── PowerLevelControl.qml │ ├── PresenceOrb.qml │ └── Theme.qml ├── DebugConsole.qml ├── Dialogs │ ├── ExportKeys.qml │ ├── HFileDialogOpener.qml │ ├── ImportKeys.qml │ └── SendFilePicker.qml ├── GlobalTapHandlers.qml ├── IdleManager.qml ├── LoadingScreen.qml ├── MainPane │ ├── AccountBar.qml │ ├── AccountContextMenu.qml │ ├── AccountDelegate.qml │ ├── BottomBar.qml │ ├── MainPane.qml │ ├── MessageIndicator.qml │ ├── RoomDelegate.qml │ ├── RoomList.qml │ └── TopBar.qml ├── ModelStore.qml ├── PageLoader.qml ├── Pages │ ├── AccountSettings │ │ ├── AccountSettings.qml │ │ ├── DeviceDelegate.qml │ │ ├── DeviceSection.qml │ │ ├── General.qml │ │ ├── Notifications.qml │ │ ├── PushRuleButton.qml │ │ ├── PushRuleDelegate.qml │ │ └── Security.qml │ ├── AddAccount │ │ ├── AddAccount.qml │ │ ├── Register.qml │ │ ├── Reset.qml │ │ ├── ServerBrowser.qml │ │ ├── ServerDelegate.qml │ │ ├── SignInBase.qml │ │ ├── SignInPassword.qml │ │ └── SignInSso.qml │ ├── AddChat │ │ ├── AddChat.qml │ │ ├── CreateRoom.qml │ │ ├── CurrentUserAvatar.qml │ │ ├── DirectChat.qml │ │ ├── EncryptCheckBox.qml │ │ └── JoinRoom.qml │ ├── Chat │ │ ├── AutoCompletion │ │ │ ├── CompletableUserDelegate.qml │ │ │ └── UserAutoCompletion.qml │ │ ├── Chat.qml │ │ ├── ChatPage.qml │ │ ├── Composer │ │ │ ├── Composer.qml │ │ │ ├── MessageArea.qml │ │ │ └── UploadButton.qml │ │ ├── FileTransfer │ │ │ ├── Transfer.qml │ │ │ └── TransferList.qml │ │ ├── InfoBar.qml │ │ ├── ReplyBar.qml │ │ ├── RoomHeader.qml │ │ ├── RoomHeaderButton.qml │ │ ├── RoomPane │ │ │ ├── MemberView │ │ │ │ ├── DeviceVerification.qml │ │ │ │ ├── MemberDelegate.qml │ │ │ │ ├── MemberDeviceDelegate.qml │ │ │ │ ├── MemberDeviceSection.qml │ │ │ │ ├── MemberProfile.qml │ │ │ │ └── MemberView.qml │ │ │ ├── RoomPane.qml │ │ │ └── SettingsView.qml │ │ ├── Timeline │ │ │ ├── DayBreak.qml │ │ │ ├── EventAudio.qml │ │ │ ├── EventContent.qml │ │ │ ├── EventContextMenu.qml │ │ │ ├── EventDelegate.qml │ │ │ ├── EventFile.qml │ │ │ ├── EventImage.qml │ │ │ ├── EventImageTextBubble.qml │ │ │ ├── EventList.qml │ │ │ ├── EventMediaLoader.qml │ │ │ └── EventVideo.qml │ │ ├── TypingMembersBar.qml │ │ └── UploadDropArea.qml │ └── Default.qml ├── Popups │ ├── ClearMessagesPopup.qml │ ├── ConfirmClipboardUploadPopup.qml │ ├── ConfirmUploadPopup.qml │ ├── DeleteDevicesPopup.qml │ ├── DetailsLabel.qml │ ├── HColumnPopup.qml │ ├── HFlickableColumnPopup.qml │ ├── HPopupShortcut.qml │ ├── IgnoreUserPopup.qml │ ├── ImageViewerPopup │ │ ├── ImageViewerPopup.qml │ │ ├── ViewerButtons.qml │ │ ├── ViewerCanvas.qml │ │ └── ViewerInfo.qml │ ├── InvalidAccessTokenPopup.qml │ ├── InviteToRoomPopup.qml │ ├── KeyVerificationPopup.qml │ ├── LeaveRoomPopup.qml │ ├── PasswordPopup.qml │ ├── Pre070SettingsDetectedPopup.qml │ ├── PushRuleSettingsPopup │ │ ├── ContentRule.qml │ │ ├── CustomLabel.qml │ │ ├── GeneralRule.qml │ │ ├── PushConditions │ │ │ ├── CustomFlow.qml │ │ │ ├── PushContainsDisplayName.qml │ │ │ ├── PushEventMatch.qml │ │ │ ├── PushRoomMemberCount.qml │ │ │ ├── PushSenderNotificationPermission.qml │ │ │ └── PushUnknownCondition.qml │ │ ├── PushRuleSettingsPopup.qml │ │ ├── RoomRule.qml │ │ └── SenderRule.qml │ ├── RedactPopup.qml │ ├── RemoveMemberPopup.qml │ ├── SignOutPopup.qml │ ├── SummaryLabel.qml │ └── UnexpectedErrorPopup.qml ├── PythonBridge │ ├── EventHandlers.qml │ ├── Globals.qml │ ├── PythonBridge.qml │ ├── PythonRootBridge.qml │ └── qmldir ├── ShortcutBundles │ ├── FlickShortcuts.qml │ └── TabShortcuts.qml ├── TrayIcon.qml ├── UI.qml ├── Utils.qml ├── Window.qml └── qmldir ├── icons └── thin │ ├── account-settings.svg │ ├── add-account.svg │ ├── add-chat.svg │ ├── apply.svg │ ├── bookmark-add.svg │ ├── bookmark-remove.svg │ ├── broken-image.svg │ ├── cancel.svg │ ├── check-mark-partial.svg │ ├── check-mark.svg │ ├── clear-messages.svg │ ├── close-view.svg │ ├── combo-box-close.svg │ ├── combo-box-open.svg │ ├── confirm-uploading-file.svg │ ├── copy-link.svg │ ├── copy-local-path.svg │ ├── copy-room-id.svg │ ├── copy-text.svg │ ├── copy-user-id.svg │ ├── cut-text.svg │ ├── debug.svg │ ├── deselect-all-messages.svg │ ├── developer-console.svg │ ├── device-action-menu.svg │ ├── device-blacklisted.svg │ ├── device-current.svg │ ├── device-delete-checked.svg │ ├── device-delete.svg │ ├── device-ignored.svg │ ├── device-refresh-list.svg │ ├── device-rename.svg │ ├── device-unset.svg │ ├── device-verified.svg │ ├── device-verify.svg │ ├── documentation.svg │ ├── download.svg │ ├── downloading.svg │ ├── drop-file-upload.svg │ ├── email.svg │ ├── every-room.svg │ ├── expand.svg │ ├── export-keys.svg │ ├── feature-unavailable-offline.svg │ ├── field-help.svg │ ├── go-back-to-chat-from-main-pane.svg │ ├── go-back-to-chat-from-room-pane.svg │ ├── go-back-to-main-pane.svg │ ├── go-to-room-pane.svg │ ├── ignore-user.svg │ ├── image-alt-scale-mode.svg │ ├── image-close.svg │ ├── image-fullscreen.svg │ ├── image-pause.svg │ ├── image-play.svg │ ├── image-rotate-left.svg │ ├── image-rotate-right.svg │ ├── image-speed.svg │ ├── import-keys.svg │ ├── invite-accept.svg │ ├── invite-decline.svg │ ├── invite-received.svg │ ├── join.svg │ ├── menu-add-chat.svg │ ├── menu-item-check-mark.svg │ ├── more-settings.svg │ ├── notifications-enable.svg │ ├── notifications-highlights-only.svg │ ├── notifications-mute.svg │ ├── ok.svg │ ├── open-externally.svg │ ├── paste-text.svg │ ├── phone.svg │ ├── player-fullscreen-exit.svg │ ├── player-fullscreen.svg │ ├── player-loop.svg │ ├── player-pause.svg │ ├── player-play.svg │ ├── player-restart.svg │ ├── player-speed.svg │ ├── player-track-audio.svg │ ├── player-track-subtitle.svg │ ├── player-track-video.svg │ ├── player-volume-high.svg │ ├── player-volume-low.svg │ ├── player-volume-mute.svg │ ├── presence-busy.svg │ ├── presence-invisible.svg │ ├── presence-offline.svg │ ├── presence-online.svg │ ├── previously-set-status.svg │ ├── pushrule-action-add.svg │ ├── pushrule-action-bubble.svg │ ├── pushrule-action-sound.svg │ ├── pushrule-action-urgency-hint.svg │ ├── pushrule-add.svg │ ├── pushrule-condition-add.svg │ ├── pushrule-condition-remove.svg │ ├── pushrule-edit.svg │ ├── pushrule-remove.svg │ ├── redo.svg │ ├── reduced-menu.svg │ ├── reduced-room-buttons.svg │ ├── register.svg │ ├── reload-config-files.svg │ ├── remove-message.svg │ ├── reply-cancel.svg │ ├── reply-to.svg │ ├── report-error.svg │ ├── reset-password.svg │ ├── retry.svg │ ├── room-ban.svg │ ├── room-create.svg │ ├── room-forget.svg │ ├── room-header-copy.svg │ ├── room-header-deselect.svg │ ├── room-header-remove.svg │ ├── room-join.svg │ ├── room-kick.svg │ ├── room-leave.svg │ ├── room-menu-notifications.svg │ ├── room-pane-expand-search.svg │ ├── room-pin.svg │ ├── room-send-invite.svg │ ├── room-unpin.svg │ ├── room-view-files.svg │ ├── room-view-history.svg │ ├── room-view-members.svg │ ├── room-view-notifications.svg │ ├── room-view-settings.svg │ ├── search.svg │ ├── select-all-text.svg │ ├── select-until-here.svg │ ├── server-connect-to-address.svg │ ├── server-ping-bad.svg │ ├── server-ping-fail.svg │ ├── server-ping-good.svg │ ├── server-ping-medium.svg │ ├── server-visit-website.svg │ ├── set-status.svg │ ├── settings.svg │ ├── sign-back-in.svg │ ├── sign-in-insecure.svg │ ├── sign-in-local-http.svg │ ├── sign-in-secure.svg │ ├── sign-in.svg │ ├── sign-out.svg │ ├── start-direct-chat.svg │ ├── status.svg │ ├── stop-ignore-user.svg │ ├── submenu-arrow.svg │ ├── theme.svg │ ├── toggle-select-message.svg │ ├── transfer-cancel.svg │ ├── transfer-pause.svg │ ├── transfer-resume.svg │ ├── tray-icon.png │ ├── typing.svg │ ├── undo.svg │ ├── unknown-devices-inspect.svg │ ├── unknown-devices-warning.svg │ ├── upload-avatar.svg │ ├── upload-file.svg │ ├── uploading.svg │ ├── user-invited.svg │ ├── user-power-100.svg │ ├── user-power-50.svg │ ├── user-power-default.svg │ └── username.svg ├── images └── midnight.jpg ├── main.cpp ├── sounds └── default.wav ├── themes ├── Glass.qpl └── Midnight.qpl └── utils.h /.flake8: -------------------------------------------------------------------------------- 1 | # vim: ft=dosini 2 | # https://flake8.pycqa.org/en/latest/user/configuration.html 3 | 4 | [flake8] 5 | # E131: continuation line unaligned for hanging indent 6 | # E301: when method is after a commented line + one blank line 7 | # E302: expected 2 blank lines, found 1 when using @dataclass 8 | # E303: more than one blank line between methods 9 | # W504: when line breaks occur after a binary operator 10 | # A003: when class attribute name is the same as a builtin 11 | # E402: when a module import isn't at the start of the file 12 | ignore = E131, E221, E241, E251, E301, E302, E303, W504, A003, E402 13 | 14 | max-complexity = 99 15 | inline-quotes = " 16 | format = ${cyan}%(path)s${reset}:${yellow_bold}%(row)d${reset}:${green_bold}%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: 'Report an unexpected behavior ' 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Description 11 | 12 | Describe your issue in details, provide error logs or screenshots if possible. 13 | 14 | ### Your environment 15 | 16 | - OS or distribution (e.g. Arch Linux, macOS 10.15, Windows 7...) 17 | - Architecture (e.g. x86 64bit) 18 | - For Linux users: your desktop environment or window manager (e.g. GNOME 3.34 Wayland, i3 4.17, etc) 19 | - How did you install Mirage? (e.g. manual build, distribution repository, AppImage, Flatpak...) 20 | - For manual installations: your Qt version 21 | - For manual installations: your Python version 22 | 23 | ### Steps to reproduce 24 | 25 | 1. Do this... 26 | 2. Do that... 27 | 28 | ### Expected behavior 29 | 30 | Tell us what should happen 31 | 32 | ### Actual behavior 33 | 34 | Tell us what happens instead 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for the application or project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about the application or project 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .mypy_cache 3 | *.egg-info 4 | *.pyc 5 | 6 | *.qmlc 7 | *.jsc 8 | 9 | *~ 10 | 11 | tmp-* 12 | 13 | build 14 | dist 15 | .qmake.stash 16 | Makefile 17 | mirage 18 | mirage.pro.user 19 | *.AppImage 20 | 21 | tags 22 | 23 | packaging/flatpak/flatpak-env 24 | packaging/flatpak/requirements.txt 25 | packaging/flatpak/flatpak-env-requirements.txt 26 | packaging/flatpak/flatpak-pip.json 27 | packaging/flatpak/flatpak-pip-generator 28 | .flatpak-builder/ 29 | flatpak-build/ 30 | 31 | docs/TODO.md 32 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/qsyncable"] 2 | path = submodules/qsyncable 3 | url = https://github.com/benlau/qsyncable 4 | 5 | [submodule "submodules/RadialBarDemo"] 6 | path = submodules/RadialBarDemo 7 | url = https://github.com/mirukana/RadialBarDemo 8 | 9 | [submodule "submodules/hsluv-c"] 10 | path = submodules/hsluv-c 11 | url = https://github.com/hsluv/hsluv-c 12 | 13 | [submodule "submodules/gel"] 14 | path = submodules/gel 15 | url = https://github.com/Cutehacks/gel 16 | 17 | [submodule "submodules/SortFilterProxyModel"] 18 | path = submodules/SortFilterProxyModel 19 | url = https://github.com/oKcerG/SortFilterProxyModel 20 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | # https://pycqa.github.io/isort/docs/configuration/options/ 2 | 3 | [settings] 4 | multi_line_output = 5 5 | include_trailing_comma = True 6 | -------------------------------------------------------------------------------- /.mypy.ini: -------------------------------------------------------------------------------- 1 | # https://mypy.readthedocs.io/en/stable/config_file.html 2 | 3 | [mypy] 4 | cache_dir = ~/.cache/mypy 5 | ignore_missing_imports = True 6 | follow_imports = silent 7 | warn_redundant_casts = True 8 | warn_unused_ignores = True 9 | warn_unreachable = True 10 | -------------------------------------------------------------------------------- /docs/screenshots/01-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/docs/screenshots/01-chat.png -------------------------------------------------------------------------------- /docs/screenshots/02-sign-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/docs/screenshots/02-sign-in.png -------------------------------------------------------------------------------- /docs/screenshots/03-account-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/docs/screenshots/03-account-settings.png -------------------------------------------------------------------------------- /docs/screenshots/04-create-room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/docs/screenshots/04-create-room.png -------------------------------------------------------------------------------- /docs/screenshots/05-main-pane-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/docs/screenshots/05-main-pane-small.png -------------------------------------------------------------------------------- /docs/screenshots/06-chat-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/docs/screenshots/06-chat-small.png -------------------------------------------------------------------------------- /docs/screenshots/07-room-pane-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/docs/screenshots/07-room-pane-small.png -------------------------------------------------------------------------------- /packaging/appimage/AppRun.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | set -e 3 | 4 | here="$(dirname "$(readlink -f "$0")")" 5 | 6 | export RESTORE_LD_LIBRARY_PATH="$LD_LIBRARY_PATH" 7 | export RESTORE_PYTHONHOME="$PYTHONHOME" 8 | export RESTORE_PYTHONUSERBASE="$PYTHONUSERBASE" 9 | 10 | export SSL_CERT_FILE="$here/usr/lib/python$PY_XY/site-packages/certifi/cacert.pem" 11 | export LD_LIBRARY_PATH="$here/usr/lib:$LD_LIBRARY_PATH" 12 | export PYTHONHOME="$here/usr" 13 | export PYTHONUSERBASE="$here/usr" 14 | 15 | cd "$here" 16 | exec "$here/usr/bin/mirage" "$@" 17 | -------------------------------------------------------------------------------- /packaging/appimage/README.md: -------------------------------------------------------------------------------- 1 | # AppImage building 2 | 3 | The image must be built on Ubuntu 16.04 Xenial, to ensure compatibility with 4 | older systems. 5 | 6 | LXD can be used to setup a suitable container from any distro. 7 | 8 | If not done already (all default settings are usually fine): 9 | 10 | lxd init 11 | 12 | Initialize a new container named `ubuntu`: 13 | 14 | lxc launch images:ubuntu/xenial/amd64 ubuntu 15 | 16 | Now, you can either clone the repo from inside the container...: 17 | 18 | lxc exec ubuntu -- apt install -y git 19 | lxc exec ubuntu -- git pull https://github.com/mirukana/mirage 20 | 21 | ...or directly copy a repository from your local filesystem inside: 22 | 23 | lxc exec ubuntu -- /bin/mkdir -p /root/mirage 24 | lxc file push -vr /* ubuntu/root/mirage 25 | 26 | Run the build script inside the container: 27 | 28 | lxc exec ubuntu -- /root/mirage/packaging/appimage/build.sh 29 | 30 | You can also start a shell inside (e.g. if something goes wrong): 31 | 32 | lxc exec ubuntu -- /bin/bash 33 | -------------------------------------------------------------------------------- /packaging/flatpak/collector.py: -------------------------------------------------------------------------------- 1 | import json 2 | import yaml 3 | 4 | with open("mirage.flatpak.base.yaml") as f: 5 | base = yaml.load(f, Loader=yaml.FullLoader) 6 | 7 | with open("flatpak-pip.json") as f: 8 | modules = json.load(f)["modules"] 9 | 10 | # set some modules in front as dependencies and dropping matrix-nio 11 | # which is declared separately 12 | front = [] 13 | back = [] 14 | for m in modules: 15 | n = m["name"] 16 | if n.startswith("python3-") and \ 17 | n[len("python3-"):] in ["cffi", "importlib-metadata", "multidict", "pytest-runner", "setuptools-scm"]: 18 | front.append(m) 19 | else: 20 | back.append(m) 21 | 22 | # replace placeholder with modules 23 | phold = None 24 | for i in range(len(base["modules"])): 25 | if base["modules"][i]["name"] == "PLACEHOLDER PYTHON DEPENDENCIES": 26 | phold = i 27 | break 28 | 29 | base["modules"] = base["modules"][:i] + front + back + base["modules"][i+1:] 30 | 31 | with open("mirage.flatpak.yaml", "w") as f: 32 | f.write(yaml.dump(base, sort_keys=False, indent=2)) 33 | -------------------------------------------------------------------------------- /packaging/flatpak/generate-flatpak-script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | set -e 3 | 4 | dir="$(dirname "$(readlink -f "$0")")" 5 | pip_generator_url='https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/master/pip/flatpak-pip-generator' 6 | 7 | cd "$dir" 8 | 9 | python3 -m venv flatpak-env 10 | export PATH="$dir/flatpak-env/bin:$PATH" 11 | 12 | pip3 install -Ur requirements.flatpak.txt 13 | pip3 install -Ur ../../requirements.txt 14 | 15 | # Freeze requirements, ignore blacklisted packages 16 | pip3 freeze | grep -v six= | grep -v matrix-nio > flatpak-env-requirements.txt 17 | 18 | # Generate flatpak requirements 19 | pip3 install requirements-parser 20 | [ ! -f flatpak-pip-generator ] && wget "$pip_generator_url" 21 | python3 flatpak-pip-generator -r flatpak-env-requirements.txt -o flatpak-pip 22 | 23 | pip3 install PyYAML 24 | python3 collector.py 25 | -------------------------------------------------------------------------------- /packaging/flatpak/requirements.flatpak.txt: -------------------------------------------------------------------------------- 1 | multidict == 4.5.2 2 | 3 | pytest-runner 4 | setuptools-scm 5 | -------------------------------------------------------------------------------- /packaging/mirage.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=Mirage 4 | GenericName=Matrix Chat Client 5 | Exec=mirage 6 | Icon=mirage 7 | Terminal=false 8 | Categories=Network;Chat;InstantMessaging;Qt; 9 | StartupWMClass=mirage 10 | -------------------------------------------------------------------------------- /packaging/mirage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/packaging/mirage.png -------------------------------------------------------------------------------- /packaging/update-appdata-releases.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import html 4 | import re 5 | from pathlib import Path 6 | 7 | root = Path(__file__).resolve().parent.parent 8 | title_pattern = re.compile(r"## (\d+\.\d+\.\d+) \((\d{4}-\d\d-\d\d)\)") 9 | release_lines = [" "] 10 | 11 | for line in (root / "docs" / "CHANGELOG.md").read_text().splitlines(): 12 | match = title_pattern.match(line) 13 | 14 | if match: 15 | args = (html.escape(match.group(1)), html.escape(match.group(2))) 16 | release_lines.append(' ' % args) 17 | 18 | appdata = root / "packaging" / "mirage.appdata.xml" 19 | in_releases = False 20 | final_lines = [] 21 | 22 | for line in appdata.read_text().splitlines(): 23 | if line == " ": 24 | in_releases = True 25 | final_lines += release_lines 26 | elif line == " ": 27 | in_releases = False 28 | 29 | if not in_releases: 30 | final_lines.append(line) 31 | 32 | appdata.write_text("\n".join(final_lines)) 33 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | remote_pdb >= 2.0.0, < 3 2 | pdbpp >= 0.10.2, < 0.11 3 | devtools >= 0.4.0, < 0.5 4 | 5 | mypy >= 0.812, < 0.900 6 | flake8 >= 3.8.4, < 4 7 | flake8-isort >= 4.0.0, < 5 8 | flake8-bugbear >= 20.1.4, < 21 9 | flake8-commas >= 2.0.0, < 3 10 | flake8-comprehensions >= 3.3.0, < 4 11 | flake8-executable >= 2.0.4, < 3 12 | flake8-logging-format >= 0.6.0, < 0.7 13 | flake8-pie >= 0.6.1, < 0.7 14 | flake8-quotes >= 3.2.0, < 4 15 | flake8-colors >= 0.1.6, < 0.2 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Pillow >= 7.0.0, < 9 2 | aiofiles >= 0.4.0, < 0.7 3 | appdirs >= 1.4.4, < 2 4 | cairosvg >= 2.4.2, < 3 5 | filetype >= 1.0.7, < 2 6 | html_sanitizer >= 1.9.1, < 2 7 | lxml >= 4.5.1, < 5 8 | mistune >= 0.8.4, < 0.9 9 | pymediainfo >= 4.2.1, < 5 10 | plyer >= 1.4.3, < 2 11 | sortedcontainers >= 2.2.2, < 3 12 | watchgod >= 0.7, < 0.8 13 | redbaron >= 0.9.2, < 1 14 | hsluv >= 5.0.0, < 6 15 | simpleaudio >= 1.0.4, < 2 16 | dbus-python >= 1.2.16, < 2; platform_system == "Linux" 17 | 18 | async_generator >= 1.10, < 2; python_version < "3.7" 19 | dataclasses >= 0.6, < 0.7; python_version < "3.7" 20 | pyfastcopy >= 1.0.3, < 2; python_version < "3.8" 21 | 22 | git+https://github.com/mirukana/matrix-nio#egg-matrix-nio[e2e] 23 | -------------------------------------------------------------------------------- /src/backend/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Mirage authors & contributors 2 | # SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | """This package provides Mirage's backend side that can interact with the UI. 5 | 6 | To learn more about how this package works, you might want to check the 7 | documentation in the following modules first: 8 | 9 | - `qml_bridge` 10 | - `backend` 11 | - `matrix_client` 12 | - `nio_callbacks` 13 | """ 14 | 15 | __app_name__ = "mirage" 16 | __display_name__ = "Mirage" 17 | __reverse_dns__ = "io.github.mirukana.mirage" 18 | __version__ = "0.7.2" 19 | -------------------------------------------------------------------------------- /src/backend/models/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Mirage authors & contributors 2 | # SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | """Provide classes related to data models shared between Python and QML.""" 5 | 6 | from typing import Tuple, Union 7 | 8 | SyncId = Union[str, Tuple[str, ...]] 9 | -------------------------------------------------------------------------------- /src/backend/pcn/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Mirage authors & contributors 2 | # SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | """Parse and operate on PCN (Python Config Notation) files.""" 5 | -------------------------------------------------------------------------------- /src/backend/pcn/globals_dict.py: -------------------------------------------------------------------------------- 1 | from collections import UserDict 2 | from typing import TYPE_CHECKING, Any, Dict, Iterator 3 | 4 | if TYPE_CHECKING: 5 | from .section import Section 6 | 7 | from .. import color 8 | 9 | PCN_GLOBALS: Dict[str, Any] = { 10 | "color": color.Color, 11 | "hsluv": color.hsluv, 12 | "hsluva": color.hsluva, 13 | "hsl": color.hsl, 14 | "hsla": color.hsla, 15 | "rgb": color.rgb, 16 | "rgba": color.rgba, 17 | } 18 | 19 | 20 | class GlobalsDict(UserDict): 21 | def __init__(self, section: "Section") -> None: 22 | super().__init__() 23 | self.section = section 24 | 25 | @property 26 | def full_dict(self) -> Dict[str, Any]: 27 | return { 28 | **PCN_GLOBALS, 29 | **(self.section.root if self.section.root else {}), 30 | **(self.section.root.globals if self.section.root else {}), 31 | "self": self.section, 32 | "parent": self.section.parent, 33 | "root": self.section.parent, 34 | **self.data, 35 | } 36 | 37 | def __getitem__(self, key: str) -> Any: 38 | return self.full_dict[key] 39 | 40 | def __iter__(self) -> Iterator[str]: 41 | return iter(self.full_dict) 42 | 43 | def __len__(self) -> int: 44 | return len(self.full_dict) 45 | 46 | def __repr__(self) -> str: 47 | return repr(self.full_dict) 48 | -------------------------------------------------------------------------------- /src/backend/pcn/property.py: -------------------------------------------------------------------------------- 1 | import re 2 | from dataclasses import dataclass, field 3 | from typing import TYPE_CHECKING, Any, Callable, Dict, Type 4 | 5 | if TYPE_CHECKING: 6 | from .section import Section 7 | 8 | TYPE_PROCESSORS: Dict[str, Callable[[Any], Any]] = { 9 | "tuple": lambda v: tuple(v), 10 | "set": lambda v: set(v), 11 | } 12 | 13 | 14 | class Unset: 15 | pass 16 | 17 | 18 | @dataclass 19 | class Property: 20 | name: str = field() 21 | annotation: str = field() 22 | expression: str = field() 23 | section: "Section" = field() 24 | value_override: Any = Unset 25 | 26 | def __get__(self, obj: "Section", objtype: Type["Section"]) -> Any: 27 | if not obj: 28 | return self 29 | 30 | if self.value_override is not Unset: 31 | return self.value_override 32 | 33 | env = obj.globals 34 | result = eval(self.expression, dict(env), env) # nosec 35 | 36 | return process_value(self.annotation, result) 37 | 38 | def __set__(self, obj: "Section", value: Any) -> None: 39 | self.value_override = value 40 | obj._edited[self.name] = value 41 | 42 | 43 | def process_value(annotation: str, value: Any) -> Any: 44 | annotation = re.sub(r"\[.*\]$", "", annotation) 45 | 46 | if annotation in TYPE_PROCESSORS: 47 | return TYPE_PROCESSORS[annotation](value) 48 | 49 | if annotation.lower() in TYPE_PROCESSORS: 50 | return TYPE_PROCESSORS[annotation.lower()](value) 51 | 52 | return value 53 | -------------------------------------------------------------------------------- /src/clipboard_image_provider.h: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | #ifndef CLIPBOARD_IMAGE_PROVIDER_H 5 | #define CLIPBOARD_IMAGE_PROVIDER_H 6 | 7 | #include 8 | #include 9 | 10 | #include "clipboard.h" 11 | 12 | 13 | class ClipboardImageProvider : public QQuickImageProvider { 14 | public: 15 | explicit ClipboardImageProvider(Clipboard *clipboard) 16 | : QQuickImageProvider(QQuickImageProvider::Image) 17 | { 18 | this->clipboard = clipboard; 19 | } 20 | 21 | QImage requestImage( 22 | const QString &id, QSize *size, const QSize &requestSize 23 | ) { 24 | Q_UNUSED(id); 25 | 26 | QImage *image = this->clipboard->qimage(); 27 | 28 | if (size) *size = image->size(); 29 | 30 | if (requestSize.width() > 0 && requestSize.height() > 0) 31 | return image->scaled( 32 | requestSize.width(), requestSize.height(), Qt::KeepAspectRatio 33 | ); 34 | 35 | return *image; 36 | } 37 | 38 | private: 39 | Clipboard *clipboard; 40 | }; 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /src/fonts/hack/bold-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/src/fonts/hack/bold-italic.ttf -------------------------------------------------------------------------------- /src/fonts/hack/bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/src/fonts/hack/bold.ttf -------------------------------------------------------------------------------- /src/fonts/hack/italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/src/fonts/hack/italic.ttf -------------------------------------------------------------------------------- /src/fonts/hack/regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/src/fonts/hack/regular.ttf -------------------------------------------------------------------------------- /src/fonts/roboto/bold-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/src/fonts/roboto/bold-italic.ttf -------------------------------------------------------------------------------- /src/fonts/roboto/bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/src/fonts/roboto/bold.ttf -------------------------------------------------------------------------------- /src/fonts/roboto/italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/src/fonts/roboto/italic.ttf -------------------------------------------------------------------------------- /src/fonts/roboto/regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/src/fonts/roboto/regular.ttf -------------------------------------------------------------------------------- /src/gui/Base/AutoDirectionLayout.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | 7 | HGridLayout { 8 | readonly property real summedImplicitWidth: 9 | utils.sumChildrenImplicitWidths(visibleChildren, columnSpacing) 10 | 11 | readonly property bool vertical: flow === HGridLayout.TopToBottom 12 | 13 | flow: 14 | width >= summedImplicitWidth ? 15 | HGridLayout.LeftToRight : 16 | HGridLayout.TopToBottom 17 | } 18 | -------------------------------------------------------------------------------- /src/gui/Base/Buttons/ApplyButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | PositiveButton { 5 | text: qsTr("Apply") 6 | icon.name: "apply" 7 | } 8 | -------------------------------------------------------------------------------- /src/gui/Base/Buttons/CancelButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | NegativeButton { 5 | text: qsTr("Cancel") 6 | icon.name: "cancel" 7 | } 8 | -------------------------------------------------------------------------------- /src/gui/Base/Buttons/FieldCopyButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import ".." 7 | 8 | HButton { 9 | property Item textControl // HTextField or HTextArea 10 | 11 | icon.name: "copy-text" 12 | iconItem.small: true 13 | 14 | toolTip.text: qsTr("Copy") 15 | toolTip.onClosed: toolTip.text = qsTr("Copy") 16 | toolTip.label.wrapMode: HLabel.NoWrap 17 | 18 | onClicked: { 19 | const oldPosition = textControl.cursorPosition 20 | textControl.selectAll() 21 | textControl.copy() 22 | textControl.deselect() 23 | textControl.cursorPosition = oldPosition 24 | 25 | toolTip.text = qsTr("Copied!") 26 | toolTip.instantShow(2000) 27 | } 28 | 29 | onActiveFocusChanged: if (! activeFocus && toolTip.visible) toolTip.hide() 30 | 31 | Layout.fillHeight: true 32 | } 33 | -------------------------------------------------------------------------------- /src/gui/Base/Buttons/FieldHelpButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import ".." 7 | 8 | HButton { 9 | property string helpText 10 | 11 | icon.name: "field-help" 12 | iconItem.small: true 13 | toolTip.text: helpText 14 | 15 | onClicked: toolTip.instantShow() 16 | onActiveFocusChanged: if (! activeFocus && toolTip.visible) toolTip.hide() 17 | 18 | Layout.fillHeight: true 19 | } 20 | -------------------------------------------------------------------------------- /src/gui/Base/Buttons/GroupButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick.Layouts 1.12 5 | import ".." 6 | 7 | HButton { 8 | Layout.preferredHeight: theme.baseElementsHeight 9 | Layout.fillWidth: true 10 | } 11 | -------------------------------------------------------------------------------- /src/gui/Base/Buttons/MiddleButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | GroupButton { 5 | icon.color: theme.colors.middleBackground 6 | } 7 | -------------------------------------------------------------------------------- /src/gui/Base/Buttons/NegativeButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | GroupButton { 5 | icon.color: theme.colors.negativeBackground 6 | } 7 | -------------------------------------------------------------------------------- /src/gui/Base/Buttons/PositiveButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | GroupButton { 5 | icon.color: theme.colors.positiveBackground 6 | } 7 | -------------------------------------------------------------------------------- /src/gui/Base/Class.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | QtObject { 7 | property string name 8 | } 9 | -------------------------------------------------------------------------------- /src/gui/Base/DelegateTransitionFixer.qml: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | 3 | import QtQuick 2.12 4 | 5 | Timer { 6 | // Sometimes and randomly, a HListView/HGridView delegate's add/populate 7 | // Transition will stop too early, leaving a stuck invisible or tiny item. 8 | // This is a workaround for this Qt bug happening despite the neccessary 9 | // Transition precautions from the docs being applied. 10 | 11 | property Item delegate: parent 12 | 13 | readonly property HNumberAnimation opacityFixer: HNumberAnimation { 14 | target: delegate 15 | property: "opacity" 16 | from: delegate.opacity 17 | to: 1 18 | } 19 | 20 | readonly property HNumberAnimation scaleFixer: HNumberAnimation { 21 | target: delegate 22 | property: "scale" 23 | from: delegate.scale 24 | to: 1 25 | } 26 | 27 | interval: theme.animationDuration * 2 28 | running: true 29 | onTriggered: { 30 | // if (delegate.opacity < 1 || delegate.scale < 1) print(delegate) 31 | if (delegate.opacity < 1) opacityFixer.start() 32 | if (delegate.scale < 1) scaleFixer.start() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/gui/Base/HBottomFocusLine.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | HRectangleBottomBorder { 7 | id: line 8 | 9 | property bool show: false 10 | 11 | transform: Scale { 12 | origin.x: line.width / 2 13 | origin.y: line.height / 2 14 | xScale: line.show ? 1 : 0 15 | 16 | Behavior on xScale { HNumberAnimation {} } 17 | } 18 | 19 | Behavior on color { HColorAnimation {} } 20 | } 21 | -------------------------------------------------------------------------------- /src/gui/Base/HBox.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | 7 | HColumnPage { 8 | implicitWidth: Math.min(parent.width, theme.controls.box.defaultWidth) 9 | padding: theme.spacing 10 | 11 | background: Rectangle { 12 | color: theme.controls.box.background 13 | radius: theme.controls.box.radius 14 | } 15 | 16 | HNumberAnimation on scale { 17 | running: true 18 | from: 0 19 | to: 1 20 | overshoot: 2 21 | } 22 | 23 | Behavior on implicitWidth { HNumberAnimation {} } 24 | Behavior on implicitHeight { HNumberAnimation {} } 25 | } 26 | -------------------------------------------------------------------------------- /src/gui/Base/HBusyIndicator.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | HCircleProgressBar { 7 | progress: 0.5 8 | 9 | label.visible: false 10 | baseCircle.strokeWidth: 2 11 | progressCircle.strokeWidth: 2 12 | 13 | HNumberAnimation on rotation { 14 | from: 0 15 | to: 360 16 | loops: Animation.Infinite 17 | duration: 600 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/gui/Base/HButtonBackground.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | 7 | Rectangle { 8 | property var button 9 | property QtObject buttonTheme 10 | property bool useFocusLine: true 11 | 12 | color: buttonTheme.background 13 | opacity: 14 | button.loading ? theme.loadingElementsOpacity : 15 | enabled ? 1 : 16 | theme.disabledElementsOpacity 17 | 18 | Behavior on opacity { HNumberAnimation {} } 19 | 20 | Rectangle { 21 | anchors.fill: parent 22 | radius: parent.radius 23 | color: 24 | button.checked ? buttonTheme.checkedOverlay : 25 | 26 | button.enabled && button.pressed ? buttonTheme.pressedOverlay : 27 | 28 | button.enabled && ! useFocusLine && button.activeFocus ? 29 | buttonTheme.hoveredOverlay : 30 | 31 | button.enabled && button.hovered ? buttonTheme.hoveredOverlay : 32 | 33 | "transparent" 34 | 35 | Behavior on color { HColorAnimation { factor: 0.5 } } 36 | } 37 | 38 | HBottomFocusLine { 39 | show: useFocusLine && button.activeFocus 40 | borderHeight: useFocusLine ? buttonTheme.focusedBorderWidth : 0 41 | color: useFocusLine ? button.focusLineColor : "transparent" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/gui/Base/HColorAnimation.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | ColorAnimation { 7 | property real factor: 1.0 8 | 9 | duration: theme.animationDuration * factor 10 | } 11 | -------------------------------------------------------------------------------- /src/gui/Base/HColumnLayout.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | 7 | ColumnLayout { 8 | spacing: 0 9 | } 10 | -------------------------------------------------------------------------------- /src/gui/Base/HColumnPage.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | HPage { 7 | id: page 8 | 9 | default property alias columnData: column.data 10 | 11 | property alias column: column 12 | 13 | implicitWidth: theme.controls.box.defaultWidth 14 | contentHeight: column.childrenRect.height 15 | 16 | HColumnLayout { 17 | id: column 18 | anchors.fill: parent 19 | spacing: theme.spacing * 1.5 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/gui/Base/HDrawerSwipeHandler.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | DragHandler { 7 | id: root 8 | 9 | property HDrawer drawer 10 | property bool swiped: false 11 | property real minimumSwipeDistance: 12 | Math.min(drawer.implicitWidth / 2, 100) * theme.uiScale 13 | 14 | readonly property HNumberAnimation hide: HNumberAnimation { 15 | target: drawer 16 | property: "position" 17 | to: 0 18 | onStopped: root.closeRequest() 19 | } 20 | 21 | readonly property HNumberAnimation cancel: HNumberAnimation { 22 | target: drawer 23 | property: "position" 24 | to: 1 25 | } 26 | 27 | signal closeRequest() 28 | 29 | target: null 30 | enabled: drawer.normalOrForceCollapse && drawer.visible 31 | 32 | onTranslationChanged: { 33 | if (hide.running || cancel.running) return 34 | 35 | drawer.position = 36 | drawer.edge === Qt.LeftEdge ? 1 + translation.x / implicitWidth : 37 | drawer.edge === Qt.RightEdge ? 1 - translation.x / implicitWidth : 38 | drawer.edge === Qt.TopEdge ? 1 - translation.y / implicitHeight : 39 | 1 + translation.y / implicitHeight 40 | 41 | const distance = Math.abs(translation[drawer.horizontal ? "x" : "y"]) 42 | 43 | if (distance > minimumSwipeDistance) swiped = true 44 | } 45 | 46 | onSwipedChanged: if (swiped) hide.start() 47 | 48 | onActiveChanged: if (! active) { 49 | if (! swiped) cancel.start() 50 | swiped = false 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/gui/Base/HFlickable.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | 7 | Flickable { 8 | id: flickable 9 | maximumFlickVelocity: window.settings.Scrolling.kinetic_max_speed 10 | flickDeceleration: window.settings.Scrolling.kinetic_deceleration 11 | 12 | ScrollBar.vertical: HScrollBar { 13 | visible: parent.interactive 14 | z: 999 15 | flickableMoving: flickable.moving 16 | } 17 | 18 | Component.onCompleted: { 19 | kineticScrollingDisabler = Qt.createComponent( 20 | "HKineticScrollingDisabler.qml" 21 | ).createObject(flickable, {flickable}) 22 | 23 | kineticScrollingDisabler.width = Qt.binding(() => 24 | kineticScrollingDisabler.enabled ? flickable.width : 0 25 | ) 26 | kineticScrollingDisabler.height = Qt.binding(() => 27 | kineticScrollingDisabler.enabled ? flickable.height : 0 28 | ) 29 | } 30 | 31 | property var kineticScrollingDisabler 32 | } 33 | -------------------------------------------------------------------------------- /src/gui/Base/HFlow.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | Flow { 7 | populate: Transition { 8 | id: addTrans 9 | 10 | SequentialAnimation { 11 | PropertyAction { property: "opacity"; value: 0 } 12 | 13 | PauseAnimation { 14 | duration: 15 | addTrans.ViewTransition.index * theme.animationDuration / 2 16 | } 17 | 18 | ParallelAnimation { 19 | HNumberAnimation { property: "opacity"; to: 1 } 20 | HNumberAnimation { properties: "x,y"; from: 0 } 21 | } 22 | } 23 | } 24 | 25 | add: Transition { 26 | ParallelAnimation { 27 | HNumberAnimation { property: "opacity"; to: 1 } 28 | HNumberAnimation { properties: "x,y"; from: 0 } 29 | } 30 | } 31 | 32 | move: Transition { 33 | ParallelAnimation { 34 | // Ensure opacity goes to 1 if add transition is interrupted 35 | HNumberAnimation { property: "opacity"; to: 1 } 36 | HNumberAnimation { properties: "x,y" } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/gui/Base/HGridLayout.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | 7 | GridLayout { 8 | rowSpacing: 0 9 | columnSpacing: 0 10 | } 11 | -------------------------------------------------------------------------------- /src/gui/Base/HIcon.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtGraphicalEffects 1.12 6 | 7 | Image { 8 | id: icon 9 | 10 | property string svgName: "" 11 | 12 | property bool small: false 13 | property int dimension: 14 | theme ? 15 | (small ? theme.icons.smallDimension : theme.icons.dimension) : 16 | (small ? 16 : 22) 17 | 18 | property color colorize: theme.icons.colorize 19 | property string iconPack: theme ? theme.icons.preferredPack : "thin" 20 | 21 | cache: true 22 | asynchronous: true 23 | fillMode: Image.PreserveAspectFit 24 | visible: Boolean(svgName) 25 | source: svgName ? `../../icons/${iconPack}/${svgName}.svg` : "" 26 | 27 | sourceSize.width: svgName ? dimension : 0 28 | sourceSize.height: svgName ? dimension : 0 29 | 30 | layer.enabled: ! Qt.colorEqual(colorize, "transparent") 31 | layer.effect: ColorOverlay { 32 | color: icon.colorize 33 | cached: icon.cache 34 | 35 | Behavior on color { HColorAnimation {} } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/gui/Base/HLabel.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick.Controls 2.12 5 | import QtQuick 2.12 6 | 7 | Label { 8 | font.family: theme.fontFamily.sans 9 | font.pixelSize: theme.fontSize.normal 10 | font.pointSize: -1 11 | textFormat: Label.PlainText 12 | horizontalAlignment: Label.AlignLeft 13 | color: theme.colors.text 14 | linkColor: theme.colors.link 15 | maximumLineCount: elide === Label.ElideNone ? Number.MAX_VALUE : 1 16 | 17 | onLinkActivated: Qt.openUrlExternally(link) 18 | } 19 | -------------------------------------------------------------------------------- /src/gui/Base/HLoader.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | Loader { 7 | id: loader 8 | asynchronous: true 9 | // visible: status === Loader.Ready 10 | } 11 | -------------------------------------------------------------------------------- /src/gui/Base/HMenuItem.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | import QtQuick.Layouts 1.12 7 | 8 | MenuItem { 9 | id: menuItem 10 | 11 | readonly property alias iconItem: contentItem.icon 12 | readonly property alias label: contentItem.label 13 | 14 | spacing: theme.spacing 15 | leftPadding: spacing 16 | rightPadding: leftPadding 17 | topPadding: spacing / 1.75 18 | bottomPadding: topPadding 19 | height: visible ? implicitHeight : 0 20 | enabled: visible // prevent focusing invisible items by using keyboard 21 | 22 | icon.color: theme.icons.colorize 23 | 24 | background: HButtonBackground { 25 | button: menuItem 26 | buttonTheme: theme.controls.menuItem 27 | useFocusLine: false 28 | } 29 | 30 | contentItem: HButtonContent { 31 | id: contentItem 32 | button: menuItem 33 | buttonTheme: theme.controls.menuItem 34 | label.horizontalAlignment: Label.AlignLeft 35 | 36 | HIcon { 37 | visible: menuItem.checkable 38 | opacity: menuItem.checked ? 1 : 0 39 | svgName: "menu-item-check-mark" 40 | 41 | Behavior on opacity { HNumberAnimation {} } 42 | } 43 | 44 | HIcon { 45 | visible: menuItem.subMenu 46 | svgName: "submenu-arrow" 47 | } 48 | } 49 | 50 | arrow: null 51 | indicator: null 52 | } 53 | -------------------------------------------------------------------------------- /src/gui/Base/HMenuItemPopupSpawner.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | 7 | HMenuItem { 8 | property var popup // url or HPopup Component 9 | property bool autoDestruct: true 10 | property var properties: ({}) 11 | 12 | onTriggered: { 13 | menu.focusOnClosed = null 14 | 15 | utils.makePopup( 16 | popup, 17 | window, 18 | utils.objectUpdate( 19 | { focusOnClosed: menu.previouslyFocused }, properties, 20 | ), 21 | null, 22 | autoDestruct, 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/gui/Base/HMenuSeparator.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | 7 | MenuSeparator { 8 | id: separator 9 | padding: 0 10 | contentItem: Item { 11 | implicitHeight: separator.visible ? theme.spacing : 0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/gui/Base/HNoticePage.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | 7 | HRowLayout { 8 | property alias label: noticeLabel 9 | property alias text: noticeLabel.text 10 | property alias color: noticeLabel.color 11 | property alias font: noticeLabel.font 12 | property alias backgroundColor: noticeLabelBackground.color 13 | property alias radius: noticeLabelBackground.radius 14 | 15 | HLabel { 16 | id: noticeLabel 17 | horizontalAlignment: Text.AlignHCenter 18 | wrapMode: HLabel.Wrap 19 | padding: theme.spacing / 2 20 | leftPadding: theme.spacing 21 | rightPadding: leftPadding 22 | 23 | opacity: width > 16 * theme.uiScale ? 1 : 0 24 | 25 | background: Rectangle { 26 | id: noticeLabelBackground 27 | color: theme.controls.box.background 28 | radius: theme.controls.box.radius 29 | } 30 | 31 | Layout.alignment: Qt.AlignCenter 32 | Layout.preferredWidth: implicitWidth 33 | Layout.maximumWidth: parent.width 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/gui/Base/HNumberAnimation.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | NumberAnimation { 7 | property real factor: 1.0 8 | property real overshoot: 0.0 9 | 10 | duration: 11 | theme.animationDuration * 12 | Math.max((1 + Math.abs(overshoot)) / 1.7, 1.0) * factor 13 | 14 | easing.type: 15 | overshoot > 0 ? Easing.OutBack : 16 | overshoot < 0 ? Easing.InBack : 17 | Easing.Linear 18 | 19 | easing.overshoot: overshoot 20 | } 21 | -------------------------------------------------------------------------------- /src/gui/Base/HPage.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | 7 | Page { 8 | property bool useVariableSpacing: true 9 | 10 | property int currentSpacing: 11 | useVariableSpacing ? 12 | Math.min( 13 | theme.spacing * width / 400, 14 | theme.spacing * height / 400, 15 | theme.spacing, 16 | ) : 17 | theme.spacing 18 | 19 | signal keyboardAccept() 20 | signal keyboardCancel() 21 | 22 | padding: currentSpacing < theme.spacing ? 0 : currentSpacing 23 | background: null 24 | 25 | Keys.onReturnPressed: keyboardAccept() 26 | Keys.onEnterPressed: keyboardAccept() 27 | Keys.onEscapePressed: keyboardCancel() 28 | 29 | Behavior on padding { HNumberAnimation {} } 30 | } 31 | -------------------------------------------------------------------------------- /src/gui/Base/HPauseAnimation.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | PauseAnimation { 7 | property real factor: 1.0 8 | 9 | duration: theme.animationDuration * factor 10 | } 11 | -------------------------------------------------------------------------------- /src/gui/Base/HProgressBar.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | 7 | ProgressBar { 8 | id: bar 9 | 10 | property color backgroundColor: theme.controls.progressBar.background 11 | property color foregroundColor: theme.controls.progressBar.foreground 12 | 13 | background: Rectangle { 14 | implicitWidth: 200 15 | implicitHeight: theme.controls.progressBar.height 16 | color: backgroundColor 17 | } 18 | 19 | contentItem: Item { 20 | implicitWidth: 200 21 | implicitHeight: theme.controls.progressBar.height 22 | 23 | Rectangle { 24 | id: indicator 25 | width: bar.indeterminate ? 26 | parent.width / 8 : bar.visualPosition * parent.width 27 | height: parent.height 28 | color: foregroundColor 29 | 30 | Behavior on color { HColorAnimation {} } 31 | 32 | HNumberAnimation on x { 33 | running: bar.visible && bar.indeterminate 34 | duration: 800 35 | from: 0 36 | to: bar.width - indicator.width 37 | 38 | onStopped: if (bar.indeterminate) { 39 | [from, to] = [to, from]; 40 | start() 41 | } else { 42 | indicator.x = 0 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/gui/Base/HQtObject.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | QtObject { 7 | default property list data 8 | } 9 | -------------------------------------------------------------------------------- /src/gui/Base/HRectangleBottomBorder.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | Item { 4 | id: root 5 | 6 | property Rectangle rectangle: parent 7 | property alias borderHeight: clipArea.height 8 | property alias color: borderRectangle.color 9 | 10 | implicitWidth: rectangle.width 11 | implicitHeight: rectangle.height 12 | 13 | Item { 14 | id: clipArea 15 | anchors.bottom: parent ? parent.bottom : undefined 16 | width: parent ? parent.width : 0 17 | height: 1 18 | clip: true 19 | 20 | Rectangle { 21 | id: borderRectangle 22 | anchors.bottom: parent ? parent.bottom : undefined 23 | width: parent ? parent.width : 0 24 | height: root.height 25 | radius: rectangle.radius 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/gui/Base/HRepeater.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick.Controls 2.12 5 | import QtQuick 2.12 6 | 7 | Repeater { 8 | id: repeater 9 | 10 | readonly property var childrenImplicitWidth: { 11 | const widths = [] 12 | 13 | for (let i = 0; i < repeater.count; i++) { 14 | const item = repeater.itemAt(i) 15 | 16 | if (item) 17 | widths.push(item.implicitWidth > 0 ? item.implicitWidth : 0) 18 | } 19 | 20 | return widths 21 | } 22 | 23 | readonly property var childrenWidth: { 24 | const widths = [] 25 | 26 | for (let i = 0; i < repeater.count; i++) { 27 | const item = repeater.itemAt(i) 28 | if (item) widths.push(item.width > 0 ? item.width : 0) 29 | } 30 | 31 | return widths 32 | } 33 | 34 | readonly property real summedWidth: utils.sum(childrenWidth) 35 | readonly property real summedImplicitWidth:utils.sum(childrenImplicitWidth) 36 | 37 | readonly property real thinestChild: Math.min(...childrenWidth) 38 | readonly property real widestChild: Math.max(...childrenWidth) 39 | } 40 | -------------------------------------------------------------------------------- /src/gui/Base/HRoomAvatar.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | HAvatar { 7 | property string roomId 8 | property string displayName 9 | 10 | name: displayName[0] === "#" && displayName.length > 1 ? 11 | displayName.substring(1) : 12 | displayName 13 | 14 | title: "room_" + roomId + ".avatar" 15 | } 16 | -------------------------------------------------------------------------------- /src/gui/Base/HRowLayout.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | 7 | RowLayout { 8 | spacing: 0 9 | } 10 | -------------------------------------------------------------------------------- /src/gui/Base/HScrollView.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | 7 | ScrollView { 8 | id: scrollView 9 | 10 | ScrollBar.vertical: HScrollBar { 11 | parent: scrollView 12 | x: scrollView.mirrored ? 0 : scrollView.width - width 13 | y: scrollView.topPadding 14 | height: scrollView.availableHeight 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/gui/Base/HSelectableLabel.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | 7 | TextEdit { 8 | id: label 9 | 10 | property bool enableLinkActivation: true 11 | 12 | // For rich text, selectedText returns some weird invisible characters 13 | // instead of real newlines 14 | readonly property string selectedPlainText: 15 | selectedText.replace(/[\u2028\u2029]/g, "\n") 16 | 17 | function selectWordAt(position) { 18 | label.cursorPosition = positionAt(position.x, position.y) 19 | label.selectWord() 20 | } 21 | 22 | function selectAllText() { 23 | label.selectAll() 24 | } 25 | 26 | font.family: theme.fontFamily.sans 27 | font.pixelSize: theme.fontSize.normal 28 | color: theme.colors.text 29 | textFormat: Label.PlainText 30 | tabStopDistance: 4 * 4 // 4 spaces 31 | horizontalAlignment: Label.AlignLeft 32 | 33 | readOnly: true 34 | activeFocusOnPress: false 35 | focus: false 36 | selectByMouse: true 37 | 38 | onLinkActivated: if (enableLinkActivation) Qt.openUrlExternally(link) 39 | 40 | MouseArea { 41 | anchors.fill: label 42 | acceptedButtons: Qt.NoButton 43 | cursorShape: label.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/gui/Base/HShortcut.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | Shortcut { 7 | // TODO: use enabled + a Binding with restoreValue when switch to Qt 5.15 8 | property bool active: true 9 | property bool disableIfAnyPopupOrMenu: true 10 | 11 | enabled: (! window.anyPopupOrMenu || ! disableIfAnyPopupOrMenu) && active 12 | context: Qt.ApplicationShortcut 13 | } 14 | -------------------------------------------------------------------------------- /src/gui/Base/HSpacer.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | 7 | Item { 8 | Layout.fillWidth: true 9 | Layout.fillHeight: true 10 | } 11 | -------------------------------------------------------------------------------- /src/gui/Base/HStackView.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | 7 | StackView { 8 | pushEnter: Transition { 9 | HNumberAnimation { 10 | property: "scale" 11 | from: 0 12 | to: 1 13 | } 14 | } 15 | 16 | pushExit: Transition { 17 | HNumberAnimation { 18 | property: "opacity" 19 | from: 1 20 | to: 0 21 | } 22 | } 23 | 24 | popEnter: Transition { 25 | HNumberAnimation { 26 | property: "opacity" 27 | from: 0 28 | to: 1 29 | } 30 | } 31 | 32 | popExit: Transition { 33 | HNumberAnimation { 34 | property: "scale" 35 | from: 1 36 | to: 0 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/gui/Base/HSwipeView.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | import "../ShortcutBundles" 7 | 8 | SwipeView { 9 | id: swipeView 10 | 11 | enum Move { ToPrevious, ToNext } 12 | 13 | property string saveName: "" 14 | property var saveId: "ALL" 15 | property var saveProperties: ["currentIndex"] 16 | 17 | // Prevent onCurrentIndexChanged from running before Component.onCompleted 18 | property bool saveEnabled: false 19 | 20 | property int previousIndex: 0 21 | property int defaultIndex: 0 22 | property int lastMove: HSwipeView.Move.ToNext 23 | property bool changed: currentIndex !== defaultIndex 24 | 25 | function reset() { setCurrentIndex(defaultIndex) } 26 | 27 | function incrementWrapIndex() { 28 | currentIndex === count - 1 ? 29 | setCurrentIndex(0) : 30 | incrementCurrentIndex() 31 | } 32 | 33 | function decrementWrapIndex() { 34 | currentIndex === 0 ? 35 | setCurrentIndex(count - 1) : 36 | decrementCurrentIndex() 37 | } 38 | 39 | Component.onCompleted: if (! changed) { 40 | setCurrentIndex(window.getState(this, "currentIndex", defaultIndex)) 41 | saveEnabled = true 42 | } 43 | 44 | onCurrentIndexChanged: { 45 | if (saveEnabled) window.saveState(this) 46 | 47 | if (currentIndex < previousIndex) lastMove = HSwipeView.Move.ToPrevious 48 | if (currentIndex > previousIndex) lastMove = HSwipeView.Move.ToNext 49 | 50 | previousIndex = currentIndex 51 | } 52 | 53 | TabShortcuts { 54 | container: swipeView 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/gui/Base/HTabBar.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | import "../ShortcutBundles" 7 | 8 | TabBar { 9 | id: tabBar 10 | 11 | property alias shortcutsEnabled: tabShortcuts.active 12 | 13 | spacing: 0 14 | position: TabBar.Header 15 | 16 | background: Item { 17 | Rectangle { 18 | width: parent.width 19 | anchors.bottom: parent.bottom 20 | height: 2 21 | color: theme.controls.tab.bottomLine 22 | } 23 | } 24 | 25 | TabShortcuts { 26 | id: tabShortcuts 27 | container: tabBar 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/gui/Base/HTile/ContentRow.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import ".." 6 | 7 | HRowLayout { 8 | property HTile tile 9 | 10 | spacing: tile.spacing 11 | opacity: tile.contentOpacity 12 | } 13 | -------------------------------------------------------------------------------- /src/gui/Base/HTile/SubtitleLabel.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import ".." 7 | 8 | HLabel { 9 | property HTile tile 10 | 11 | textFormat: Text.StyledText 12 | font.pixelSize: theme.fontSize.small 13 | verticalAlignment: Qt.AlignVCenter 14 | elide: Text.ElideRight 15 | color: theme.colors.dimText 16 | visible: Layout.maximumHeight > 0 17 | 18 | Layout.maximumHeight: ! tile.compact && text ? implicitHeight : 0 19 | Layout.fillWidth: true 20 | Layout.fillHeight: true 21 | 22 | Behavior on Layout.maximumHeight { HNumberAnimation {} } 23 | 24 | MouseArea { 25 | anchors.fill: parent 26 | acceptedButtons: Qt.NoButton 27 | cursorShape: 28 | parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/gui/Base/HTile/TitleLabel.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import ".." 7 | 8 | HLabel { 9 | elide: Text.ElideRight 10 | verticalAlignment: Qt.AlignVCenter 11 | 12 | Layout.fillWidth: true 13 | Layout.fillHeight: true 14 | } 15 | -------------------------------------------------------------------------------- /src/gui/Base/HTile/TitleRightInfoLabel.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import ".." 7 | 8 | HLabel { 9 | property HTile tile 10 | property int hideUnderWidth: 200 11 | 12 | font.pixelSize: theme.fontSize.small 13 | verticalAlignment: Qt.AlignVCenter 14 | color: theme.colors.halfDimText 15 | opacity: Layout.maximumWidth > 0 ? 1 : 0 16 | visible: opacity > 0 17 | 18 | Layout.fillHeight: true 19 | Layout.maximumWidth: 20 | text && tile.width >= hideUnderWidth * theme.uiScale ? 21 | implicitWidth : 0 22 | 23 | Behavior on Layout.maximumWidth { HNumberAnimation {} } 24 | } 25 | -------------------------------------------------------------------------------- /src/gui/Base/MediaPlayer/AudioPlayer.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtAV 1.7 6 | 7 | OSD { 8 | id: osd 9 | 10 | property alias source: audioPlayer.source 11 | 12 | audioOnly: true 13 | media: audioPlayer 14 | 15 | implicitWidth: osd.width 16 | implicitHeight: osd.height 17 | 18 | MediaPlayer { 19 | id: audioPlayer 20 | autoLoad: window.settings.media.autoLoad 21 | autoPlay: window.settings.media.autoPlay 22 | volume: window.settings.media.defaultVolume / 100 23 | muted: window.settings.media.startMuted 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/gui/Base/MediaPlayer/OSDButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "../../Base" 6 | 7 | HButton { 8 | backgroundColor: "transparent" 9 | iconItem.dimension: theme.mediaPlayer.controls.iconSize 10 | } 11 | -------------------------------------------------------------------------------- /src/gui/Base/MediaPlayer/OSDLabel.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../Base" 7 | 8 | HLabel { 9 | Layout.leftMargin: theme.spacing / 2 10 | Layout.rightMargin: Layout.leftMargin 11 | } 12 | -------------------------------------------------------------------------------- /src/gui/Base/MultiviewPane.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | import QtQuick.Layouts 1.12 7 | 8 | HDrawer { 9 | id: pane 10 | 11 | default property alias swipeViewData: swipeView.contentData 12 | 13 | property color buttonsBackgroundColor 14 | 15 | property int buttonWidth: 16 | buttonRepeater.count > 0 ? buttonRepeater.itemAt(0).implicitWidth : 0 17 | 18 | readonly property alias contentTranslation: contentTranslation 19 | readonly property alias buttonRepeater: buttonRepeater 20 | readonly property alias swipeView: swipeView 21 | 22 | defaultSize: buttonRepeater.count * buttonWidth 23 | minimumSize: buttonWidth 24 | 25 | HColumnLayout { 26 | anchors.fill: parent 27 | transform: Translate { id: contentTranslation } 28 | 29 | Rectangle { 30 | color: buttonsBackgroundColor 31 | 32 | Layout.fillWidth: true 33 | Layout.preferredHeight: childrenRect.height 34 | 35 | HFlow { 36 | id: buttonFlow 37 | width: parent.width 38 | populate: null 39 | 40 | Repeater { 41 | id: buttonRepeater 42 | } 43 | } 44 | } 45 | 46 | HSwipeView { 47 | id: swipeView 48 | clip: true 49 | interactive: ! pane.collapsed 50 | 51 | Layout.fillWidth: true 52 | Layout.fillHeight: true 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/gui/Base/PresenceOrb.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | Rectangle { 7 | property string presence 8 | 9 | implicitWidth: 10 | window.settings.General.compact ? 11 | theme.controls.presence.radius * 2 : 12 | theme.controls.presence.radius * 2.5 13 | 14 | implicitHeight: width 15 | radius: width / 2 16 | opacity: theme.controls.presence.opacity 17 | 18 | color: 19 | presence.includes("online") ? 20 | theme.controls.presence.online : 21 | 22 | presence.includes("unavailable") ? 23 | theme.controls.presence.unavailable : 24 | 25 | theme.controls.presence.offline 26 | 27 | border.color: theme.controls.presence.border 28 | border.width: theme.controls.presence.borderWidth 29 | 30 | Behavior on color { HColorAnimation {} } 31 | Behavior on opacity { HNumberAnimation {} } 32 | 33 | HoverHandler { id: presenceHover } 34 | 35 | HToolTip { 36 | visible: presenceHover.hovered 37 | text: qsTr("%1 (%2)").arg( 38 | presence.includes("online") ? qsTr("Online") : 39 | presence.includes("unavailable") ? qsTr("Unavailable") : 40 | presence.includes("invisible") ? qsTr("Invisible") : 41 | qsTr("Offline") 42 | ).arg("unknown to server") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/gui/Base/Theme.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | QtObject { 7 | id: root 8 | 9 | property Item target 10 | default property list classes 11 | 12 | readonly property var matchablePathRegex: utils.getClassPathRegex(target) 13 | readonly property var themeRules: window.themeRules 14 | readonly property var data: { 15 | const newData = {} 16 | 17 | for (const [path, section] of Object.entries(themeRules)) 18 | if (matchablePathRegex.test(path)) 19 | for (const [name, value] of Object.entries(section)) 20 | if (! name.startsWith("_")) 21 | newData[name] = value 22 | 23 | return newData 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/gui/Dialogs/ExportKeys.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import Qt.labs.platform 1.1 6 | import "../Popups" 7 | 8 | HFileDialogOpener { 9 | // This is used for the SignOutPopup to know when the export is done 10 | // so it can close 11 | signal done() 12 | 13 | property string userId: "" 14 | property bool exporting: false 15 | 16 | function exportKeys(file, passphrase) { 17 | exporting = true 18 | 19 | const path = file.toString().replace(/^file:\/\//, "") 20 | 21 | py.callClientCoro(userId, "export_keys", [path, passphrase], () => { 22 | exporting = false 23 | done() 24 | }) 25 | } 26 | 27 | fill: false 28 | dialog.title: qsTr("Save decryption keys file as...") 29 | dialog.fileMode: FileDialog.SaveFile 30 | onFilePicked: { 31 | exportPasswordPopup.file = file 32 | exportPasswordPopup.open() 33 | } 34 | 35 | PasswordPopup { 36 | id: exportPasswordPopup 37 | 38 | property url file: "" 39 | 40 | summary.text: qsTr("Passphrase to protect this file:") 41 | validateButton.text: qsTr("Export") 42 | validateButton.icon.name: "export-keys" 43 | 44 | onAcceptedPasswordChanged: exportKeys(file, acceptedPassword) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/gui/Dialogs/SendFilePicker.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import Qt.labs.platform 1.1 6 | 7 | HFileDialogOpener { 8 | property string userId 9 | property string roomId 10 | property string replyToEventId: "" 11 | property bool destroyWhenDone: false 12 | 13 | signal replied() 14 | 15 | fill: false 16 | dialog.title: qsTr("Select a file to send") 17 | dialog.fileMode: FileDialog.OpenFiles 18 | 19 | onFilesPicked: { 20 | for (const file of files) { 21 | const path = Qt.resolvedUrl(file).replace(/^file:/, "") 22 | const args = [roomId, path, replyToEventId || undefined] 23 | 24 | py.callClientCoro(userId, "send_file", args, () => { 25 | if (destroyWhenDone) destroy() 26 | 27 | }, (type, args, error, traceback) => { 28 | console.error(`python:\n${traceback}`) 29 | if (destroyWhenDone) destroy() 30 | }) 31 | 32 | if (replyToUserId) replied() 33 | } 34 | } 35 | 36 | onCancelled: if (destroyWhenDone) destroy() 37 | } 38 | -------------------------------------------------------------------------------- /src/gui/GlobalTapHandlers.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | 6 | Item { 7 | id: root 8 | 9 | property PageLoader pageLoader 10 | 11 | // Raise our z-index as much as possible, so that mouse events go before 12 | // anything else through this item which the TapHandlers are watching 13 | z: 99999 14 | 15 | implicitWidth: parent ? parent.width : 0 16 | implicitHeight: parent ? parent.height : 0 17 | 18 | TapHandler { 19 | acceptedButtons: Qt.BackButton 20 | onTapped: root.pageLoader.moveThroughHistory(1) 21 | } 22 | 23 | TapHandler { 24 | acceptedButtons: Qt.ForwardButton 25 | onTapped: root.pageLoader.moveThroughHistory(-1) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/gui/IdleManager.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import CppUtils 0.1 6 | import "." 7 | 8 | Timer { 9 | readonly property ListModel accounts: ModelStore.get("accounts") 10 | readonly property var accountsSet: new Set() 11 | 12 | function setPresence(userId, presence) { 13 | py.callClientCoro(userId, "set_presence", [presence, undefined, false]) 14 | } 15 | 16 | interval: 1000 17 | repeat: true 18 | running: 19 | window.settings.Presence.auto_away_after > 0 && 20 | CppUtils.idleMilliseconds() !== -1 21 | 22 | onTriggered: { 23 | let changes = false 24 | 25 | const beUnavailable = 26 | CppUtils.idleMilliseconds() / 1000 >= 27 | window.settings.Presence.auto_away_after 28 | 29 | for (let i = 0; i < accounts.count; i++) { 30 | const account = accounts.get(i) 31 | 32 | if (! account.presence_support) continue 33 | 34 | if (beUnavailable && account.presence === "online") { 35 | setPresence(account.id, "unavailable") 36 | accountsSet.add(account.id) 37 | changes = true 38 | 39 | } else if (! beUnavailable && accountsSet.has(account.id)) { 40 | setPresence(account.id, "online") 41 | accountsSet.delete(account.id) 42 | changes = true 43 | } 44 | } 45 | 46 | if (changes) accountsSetChanged() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/gui/LoadingScreen.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "Base" 6 | 7 | Rectangle { 8 | color: utils.hsluv(0, 0, 0, 0.7) 9 | 10 | HBusyIndicator { 11 | anchors.centerIn: parent 12 | width: Math.min(160, parent.width - 16, parent.height - 16) 13 | height: width 14 | 15 | // Because the theme is not loaded at this point, we must set these 16 | // properties manually: 17 | baseCircle.strokeColor: utils.hsluv(240, 60 / 1.5 * 2, 0, 0.7) 18 | progressCircle.strokeColor: utils.hsluv(240, 60 * 1.5, 72) 19 | label.font.family: "Roboto" 20 | label.font.pixelSize: 0 21 | label.color: "black" 22 | label.linkColor: "black" 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/gui/ModelStore.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | pragma Singleton 5 | import QtQuick 2.12 6 | import "PythonBridge" 7 | 8 | QtObject { 9 | property QtObject privates: QtObject { 10 | readonly property var store: ({}) 11 | 12 | readonly property PythonBridge py: PythonBridge {} 13 | 14 | readonly property Component model: Component { 15 | ListModel { 16 | property var modelId 17 | property var idToItems: ({}) 18 | 19 | // Used by HFilterModel 20 | signal fieldsChanged(int index, var changes) 21 | 22 | function findIndex(id, default_=null) { 23 | for (let i = 0; i < count; i++) 24 | if (get(i).id === id) return i 25 | 26 | return default_ 27 | } 28 | 29 | function find(id, default_=null) { 30 | return idToItems[id] || default_ 31 | } 32 | } 33 | } 34 | 35 | signal ensureModelExists(var modelId) 36 | 37 | onEnsureModelExists: 38 | py.callCoro("models.ensure_exists_from_qml", [modelId]) 39 | } 40 | 41 | function get(...modelId) { 42 | if (modelId.length === 1) modelId = modelId[0] 43 | 44 | if (! privates.store[modelId]) { 45 | // Using a signal somehow avoids a binding loop 46 | privates.ensureModelExists(modelId) 47 | 48 | privates.store[modelId] = 49 | privates.model.createObject(this, {modelId}) 50 | } 51 | 52 | return privates.store[modelId] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/gui/Pages/AccountSettings/AccountSettings.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | import QtQuick.Layouts 1.12 7 | import "../.." 8 | import "../../Base" 9 | 10 | HPage { 11 | id: page 12 | 13 | property string userId 14 | 15 | HTabbedBox { 16 | anchors.centerIn: parent 17 | width: Math.min(implicitWidth, page.availableWidth) 18 | height: Math.min(implicitHeight, page.availableHeight) 19 | 20 | showBackButton: mainUI.mainPane.normalOrForceCollapse 21 | backButton.icon.name: "go-back-to-main-pane" 22 | backButton.toolTip.text: qsTr("Back to main pane") 23 | backButton.onClicked: mainUI.mainPane.toggleFocus() 24 | 25 | tabBar: HTabBar { 26 | HTabButton { text: qsTr("General") } 27 | HTabButton { text: qsTr("Notifications") } 28 | HTabButton { text: qsTr("Security") } 29 | } 30 | 31 | General { userId: page.userId } 32 | Notifications { userId: page.userId } 33 | Security { userId: page.userId } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/gui/Pages/AccountSettings/PushRuleButton.qml: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-or-later 2 | 3 | import QtQuick 2.12 4 | import "../../Base" 5 | 6 | HButton { 7 | property string toggles: "" 8 | property var nextValue: ! on 9 | property HButton requiresOn: null 10 | 11 | readonly property bool on: (requiresOn === null || requiresOn.on) && ( 12 | toggles && page.pendingEdits[model.id] && 13 | toggles in page.pendingEdits[model.id] ? 14 | Boolean(page.pendingEdits[model.id][toggles]) : 15 | 16 | toggles ? 17 | Boolean(model[toggles]) : 18 | 19 | true 20 | ) 21 | 22 | opacity: on ? 1 : theme.disabledElementsOpacity 23 | hoverEnabled: true 24 | backgroundColor: "transparent" 25 | 26 | onClicked: { 27 | if (requiresOn !== null && ! requiresOn.on) { 28 | requiresOn.clicked() 29 | if (! on) clicked() 30 | return 31 | } 32 | 33 | if (! toggles) return 34 | 35 | if (! (model.id in page.pendingEdits)) page.pendingEdits[model.id] = {} 36 | 37 | if ((! on) === Boolean(model[toggles])) 38 | delete page.pendingEdits[model.id][toggles] 39 | else 40 | page.pendingEdits[model.id][toggles] = nextValue 41 | 42 | if (! Object.keys(page.pendingEdits[model.id]).length) 43 | delete page.pendingEdits[model.id] 44 | 45 | page.pendingEditsChanged() 46 | } 47 | 48 | Behavior on opacity { HNumberAnimation {} } 49 | } 50 | -------------------------------------------------------------------------------- /src/gui/Pages/AddAccount/Register.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../Base" 7 | import "../../Base/Buttons" 8 | 9 | HFlickableColumnPage { 10 | function takeFocus() { registerButton.forceActiveFocus() } 11 | 12 | footer: AutoDirectionLayout { 13 | ApplyButton { 14 | id: registerButton 15 | text: qsTr("Register from Riot") 16 | icon.name: "register" 17 | onClicked: Qt.openUrlExternally("https://riot.im/app/#/register") 18 | 19 | Layout.fillWidth: true 20 | } 21 | } 22 | 23 | HLabel { 24 | wrapMode: HLabel.Wrap 25 | horizontalAlignment: Qt.AlignHCenter 26 | text: qsTr( 27 | "Not implemented yet\n\n" + 28 | "You can create a new account from another client such as Riot." 29 | ) 30 | 31 | Layout.fillWidth: true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/gui/Pages/AddAccount/Reset.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../Base" 7 | import "../../Base/Buttons" 8 | 9 | HFlickableColumnPage { 10 | function takeFocus() { resetButton.forceActiveFocus() } 11 | 12 | footer: AutoDirectionLayout { 13 | ApplyButton { 14 | id: resetButton 15 | text: qsTr("Reset password from Riot") 16 | icon.name: "reset-password" 17 | onClicked: 18 | Qt.openUrlExternally("https://riot.im/app/#/forgot_password") 19 | 20 | Layout.fillWidth: true 21 | } 22 | } 23 | 24 | HLabel { 25 | wrapMode: HLabel.Wrap 26 | horizontalAlignment: Qt.AlignHCenter 27 | text: qsTr( 28 | "Not implemented yet\n\n" + 29 | "You can reset your password from another client such as Riot." 30 | ) 31 | 32 | Layout.fillWidth: true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/gui/Pages/AddChat/AddChat.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../Base" 7 | 8 | HPage { 9 | id: page 10 | 11 | property string userId 12 | 13 | HTabbedBox { 14 | anchors.centerIn: parent 15 | width: Math.min(implicitWidth, page.availableWidth) 16 | height: Math.min(implicitHeight, page.availableHeight) 17 | 18 | showBackButton: mainUI.mainPane.normalOrForceCollapse 19 | backButton.icon.name: "go-back-to-main-pane" 20 | backButton.toolTip.text: qsTr("Back to main pane") 21 | backButton.onClicked: mainUI.mainPane.toggleFocus() 22 | 23 | tabBar: HTabBar { 24 | HTabButton { text: qsTr("Direct chat") } 25 | HTabButton { text: qsTr("Join group") } 26 | HTabButton { text: qsTr("Create group") } 27 | } 28 | 29 | DirectChat { userId: page.userId } 30 | JoinRoom { userId: page.userId } 31 | CreateRoom { userId: page.userId } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/gui/Pages/AddChat/CurrentUserAvatar.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../Base" 7 | 8 | HUserAvatar { 9 | property QtObject account 10 | 11 | clientUserId: userId 12 | displayName: account ? account.display_name : "" 13 | mxc: account ? account.avatar_url : "" 14 | 15 | Layout.alignment: Qt.AlignCenter 16 | Layout.preferredWidth: 128 17 | Layout.preferredHeight: Layout.preferredWidth 18 | } 19 | -------------------------------------------------------------------------------- /src/gui/Pages/AddChat/EncryptCheckBox.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "../../Base" 6 | 7 | HCheckBox { 8 | text: qsTr("Encrypt messages") 9 | subtitle.textFormat: Text.StyledText 10 | subtitle.text: 11 | qsTr("Only users you trust can decrypt the conversation") + 12 | `
` + 13 | qsTr("Cannot be disabled later!") + 14 | "" 15 | } 16 | -------------------------------------------------------------------------------- /src/gui/Pages/Chat/Composer/UploadButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import Clipboard 0.1 6 | import CppUtils 0.1 7 | import "../../../Base" 8 | import "../../../Dialogs" 9 | 10 | HButton { 11 | enabled: chat.roomInfo.can_send_messages 12 | icon.name: "upload-file" 13 | toolTip.text: 14 | chat.userInfo.max_upload_size ? 15 | qsTr("Send files (%1 max)").arg( 16 | CppUtils.formattedBytes(chat.userInfo.max_upload_size, 0), 17 | ) : 18 | qsTr("Send files") 19 | 20 | onClicked: sendFilePicker.dialog.open() 21 | 22 | HShortcut { 23 | sequences: window.settings.Keys.Chat.send_clipboard_path 24 | onActivated: window.makePopup( 25 | "Popups/ConfirmUploadPopup.qml", 26 | { 27 | userId: chat.userId, 28 | roomId: chat.roomId, 29 | roomName: chat.roomInfo.display_name, 30 | filePath: Clipboard.text.trim(), 31 | replyToEventId: chat.replyToEventId, 32 | }, 33 | popup => popup.replied.connect(chat.clearReplyTo), 34 | ) 35 | } 36 | 37 | SendFilePicker { 38 | id: sendFilePicker 39 | userId: chat.userId 40 | roomId: chat.roomId 41 | replyToEventId: chat.replyToEventId 42 | onReplied: chat.clearReplyTo() 43 | 44 | HShortcut { 45 | sequences: window.settings.Keys.Chat.send_file 46 | onActivated: sendFilePicker.dialog.open() 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/gui/Pages/Chat/FileTransfer/TransferList.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "../../.." 6 | import "../../../Base" 7 | 8 | Rectangle { 9 | property int delegateHeight: 0 10 | 11 | readonly property var firstDelegate: 12 | transferList.contentItem.visibleChildren[0] 13 | 14 | readonly property alias transferCount: transferList.count 15 | 16 | implicitWidth: 800 17 | implicitHeight: firstDelegate ? firstDelegate.height : 0 18 | color: theme.chat.fileTransfer.background 19 | opacity: implicitHeight ? 1 : 0 20 | clip: true 21 | 22 | Behavior on implicitHeight { HNumberAnimation {} } 23 | 24 | HListView { 25 | id: transferList 26 | anchors.fill: parent 27 | 28 | model: ModelStore.get(chat.roomId, "transfers") 29 | delegate: Transfer { width: transferList.width } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/gui/Pages/Chat/InfoBar.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../Base" 7 | 8 | Rectangle { 9 | default property alias rowLayoutData: rowLayout.data 10 | 11 | readonly property alias icon: icon 12 | readonly property alias label: label 13 | 14 | implicitHeight: label.text ? rowLayout.height : 0 15 | opacity: implicitHeight ? 1 : 0 16 | 17 | Behavior on implicitHeight { HNumberAnimation {} } 18 | 19 | HRowLayout { 20 | id: rowLayout 21 | width: parent.width 22 | spacing: theme.spacing 23 | 24 | HIcon { 25 | id: icon 26 | 27 | Layout.fillHeight: true 28 | Layout.leftMargin: rowLayout.spacing / 2 29 | } 30 | 31 | HLabel { 32 | id: label 33 | elide: Text.ElideRight 34 | verticalAlignment: Text.AlignVCenter 35 | 36 | Layout.fillWidth: true 37 | Layout.fillHeight: true 38 | Layout.topMargin: rowLayout.spacing / 4 39 | Layout.bottomMargin: rowLayout.spacing / 4 40 | Layout.leftMargin: rowLayout.spacing / 2 41 | Layout.rightMargin: rowLayout.spacing / 2 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/gui/Pages/Chat/ReplyBar.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../Base" 7 | 8 | InfoBar { 9 | property string replyToEventId: "" 10 | property string replyToUserId: "" 11 | property string replyToDisplayName: "" 12 | 13 | signal cancel() 14 | 15 | color: theme.chat.replyBar.background 16 | icon.svgName: "reply-to" 17 | label.textFormat: Text.StyledText 18 | label.text: 19 | replyToEventId ? 20 | utils.coloredNameHtml(replyToDisplayName, replyToUserId) : 21 | "" 22 | 23 | HButton { 24 | backgroundColor: "transparent" 25 | icon.name: "reply-cancel" 26 | icon.color: theme.colors.negativeBackground 27 | topPadding: 0 28 | bottomPadding: 0 29 | onClicked: cancel() 30 | 31 | Layout.fillHeight: true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/gui/Pages/Chat/RoomHeaderButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../Base" 7 | 8 | HButton { 9 | property bool show: true 10 | 11 | visible: Layout.preferredWidth > 0 12 | 13 | Layout.preferredWidth: show ? implicitWidth : 0 14 | Layout.fillHeight: true 15 | 16 | Behavior on Layout.preferredWidth { HNumberAnimation {} } 17 | } 18 | -------------------------------------------------------------------------------- /src/gui/Pages/Chat/RoomPane/MemberView/MemberDeviceSection.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../../../Base" 7 | 8 | HRowLayout { 9 | spacing: theme.spacing / 2 10 | 11 | HIcon { 12 | svgName: "device-" + section 13 | colorize: 14 | section === "verified" ? theme.colors.positiveText : 15 | section === "blacklisted" ? theme.colors.errorText : 16 | theme.colors.warningText 17 | 18 | Layout.preferredHeight: dimension 19 | Layout.leftMargin: theme.spacing / 2 20 | } 21 | 22 | HLabel { 23 | elide: HLabel.ElideRight 24 | verticalAlignment: Qt.AlignVCenter 25 | 26 | text: 27 | section === "unset" ? qsTr("Unverified sessions") : 28 | section === "verified" ? qsTr("Verified sessions") : 29 | section === "ignored" ? qsTr("Ignored sessions") : 30 | qsTr("Blacklisted sessions") 31 | 32 | Layout.fillWidth: true 33 | Layout.fillHeight: true 34 | Layout.topMargin: theme.spacing 35 | Layout.bottomMargin: theme.spacing 36 | Layout.rightMargin: theme.spacing / 2 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/gui/Pages/Chat/Timeline/DayBreak.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "../../../Base" 6 | 7 | HNoticePage { 8 | text: model.date.toLocaleDateString() 9 | color: theme.chat.daybreak.text 10 | backgroundColor: theme.chat.daybreak.background 11 | radius: theme.chat.daybreak.radius 12 | } 13 | -------------------------------------------------------------------------------- /src/gui/Pages/Chat/Timeline/EventAudio.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import QtAV 1.7 7 | import "../../.." 8 | import "../../../Base" 9 | import "../../../Base/MediaPlayer" 10 | 11 | AudioPlayer { 12 | readonly property bool hovered: hover.hovered 13 | 14 | HoverHandler { id: hover } 15 | } 16 | -------------------------------------------------------------------------------- /src/gui/Pages/Chat/Timeline/EventImageTextBubble.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "../../../Base" 6 | 7 | HLabel { 8 | id: bubble 9 | 10 | anchors.margins: theme.spacing / 4 11 | 12 | topPadding: theme.spacing / 2 13 | bottomPadding: topPadding 14 | leftPadding: theme.spacing / 1.5 15 | rightPadding: leftPadding 16 | 17 | font.pixelSize: theme.fontSize.small 18 | 19 | background: Rectangle { 20 | color: Qt.hsla(0, 0, 0, 0.7) 21 | radius: theme.radius 22 | } 23 | 24 | Binding on visible { 25 | value: false 26 | when: ! Boolean(bubble.text) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/gui/Pages/Chat/Timeline/EventMediaLoader.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "../../.." 6 | import "../../../Base" 7 | 8 | HLoader { 9 | id: loader 10 | 11 | property QtObject singleMediaInfo 12 | property string mediaUrl 13 | property string showSender: "" 14 | property string showDate: "" 15 | property string showLocalEcho: "" 16 | 17 | readonly property string title: 18 | singleMediaInfo.media_title || utils.urlFileName(mediaUrl) 19 | 20 | readonly property string thumbnailTitle: 21 | eventList.getThumbnailTitle(singleMediaInfo) 22 | 23 | readonly property bool isMedia: 24 | eventList.getMediaType(singleMediaInfo) !== null 25 | 26 | readonly property int type: 27 | isMedia ? 28 | eventList.getMediaType(singleMediaInfo) : 29 | utils.getLinkType(mediaUrl) 30 | 31 | readonly property string cachedLocalPath: "" 32 | readonly property string thumbnailMxc: singleMediaInfo.thumbnail_url 33 | 34 | readonly property bool hovered: item ? item.hovered : false 35 | 36 | visible: Boolean(item) 37 | x: eventContent.spacing 38 | 39 | onTypeChanged: { 40 | if (type === Utils.Media.Image) { 41 | var file = "EventImage.qml" 42 | 43 | } else if (type !== Utils.Media.Page) { 44 | var file = "EventFile.qml" 45 | 46 | } else { return } 47 | 48 | loader.setSource(file, {loader}) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/gui/Pages/Chat/Timeline/EventVideo.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import QtAV 1.7 7 | import "../../.." 8 | import "../../../Base" 9 | import "../../../Base/MediaPlayer" 10 | 11 | VideoPlayer { 12 | readonly property bool hovered: hover.hovered 13 | 14 | HoverHandler { id: hover } 15 | } 16 | -------------------------------------------------------------------------------- /src/gui/Pages/Chat/TypingMembersBar.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../Base" 7 | 8 | InfoBar { 9 | property var typingMembers: [] 10 | 11 | color: theme.chat.typingMembers.background 12 | icon.svgName: "typing" // TODO: animate 13 | label.textFormat: Text.StyledText 14 | label.text: { 15 | const tm = typingMembers 16 | if (tm.length === 0) return "" 17 | if (tm.length === 1) return qsTr("%1 is typing...").arg(tm[0]) 18 | return qsTr("%1 are typing...").arg(utils.commaAndJoin(tm)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/gui/Pages/Default.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import "../Base" 5 | 6 | HNoticePage { 7 | text: qsTr("No chat selected") 8 | } 9 | -------------------------------------------------------------------------------- /src/gui/Popups/ClearMessagesPopup.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "../Base" 6 | import "../Base/Buttons" 7 | 8 | HFlickableColumnPopup { 9 | id: popup 10 | 11 | property string userId: "" 12 | property string roomId: "" 13 | property var preClearCallback: null 14 | 15 | page.footer: AutoDirectionLayout { 16 | ApplyButton { 17 | id: clearButton 18 | text: qsTr("Clear") 19 | icon.name: "clear-messages" 20 | onClicked: { 21 | if (preClearCallback) preClearCallback() 22 | py.callClientCoro(userId, "clear_events", [roomId]) 23 | popup.close() 24 | } 25 | } 26 | 27 | CancelButton { 28 | onClicked: popup.close() 29 | } 30 | } 31 | 32 | SummaryLabel { 33 | text: qsTr("Clear this room's messages?") 34 | } 35 | 36 | DetailsLabel { 37 | text: qsTr( 38 | "The messages will only be removed on your side. " + 39 | "They will be available again after you restart the application." 40 | ) 41 | } 42 | 43 | onOpened: clearButton.forceActiveFocus() 44 | } 45 | -------------------------------------------------------------------------------- /src/gui/Popups/DeleteDevicesPopup.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "../Base" 6 | import "../Base/Buttons" 7 | 8 | PasswordPopup { 9 | id: popup 10 | 11 | property string userId 12 | property var deviceIds // array 13 | property var deletedCallback: null 14 | 15 | property string deleteFutureId: "" 16 | 17 | function verifyPassword(pass, callback) { 18 | deleteFutureId = py.callClientCoro( 19 | userId, 20 | "delete_devices_with_password", 21 | [deviceIds, pass], 22 | () => { 23 | deleteFutureId = "" 24 | callback(true) 25 | }, 26 | (type, args) => { 27 | callback( 28 | type === "MatrixUnauthorized" ? 29 | false : 30 | qsTr("Unknown error: %1 - %2").arg(type).arg(args) 31 | ) 32 | }, 33 | ) 34 | } 35 | 36 | summary.text: 37 | qsTr("Enter your account's password to continue:") 38 | 39 | validateButton.text: 40 | deviceIds.length > 1 ? 41 | qsTr("Sign out %1 devices").arg(deviceIds.length) : 42 | qsTr("Sign out %1 device").arg(deviceIds.length) 43 | 44 | validateButton.icon.name: "sign-out" 45 | 46 | onClosed: { 47 | if (deleteFutureId) py.cancelCoro(deleteFutureId) 48 | 49 | if (deleteFutureId || acceptedPassword && deletedCallback) 50 | deletedCallback() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/gui/Popups/DetailsLabel.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../Base" 7 | 8 | HLabel { 9 | wrapMode: HLabel.Wrap 10 | visible: Boolean(text) 11 | 12 | Layout.fillWidth: true 13 | } 14 | -------------------------------------------------------------------------------- /src/gui/Popups/HColumnPopup.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "../Base" 6 | 7 | HPopup { 8 | id: popup 9 | 10 | default property alias pageData: page.columnData 11 | 12 | property int contentWidthLimit: theme.controls.popup.defaultWidth 13 | 14 | readonly property alias page: page 15 | 16 | signal keyboardAccept() 17 | 18 | HColumnPage { 19 | id: page 20 | implicitWidth: Math.min( 21 | popup.maximumPreferredWidth, popup.contentWidthLimit, 22 | ) 23 | implicitHeight: Math.min( 24 | popup.maximumPreferredHeight, 25 | implicitHeaderHeight + implicitFooterHeight + 26 | topPadding + bottomPadding + implicitContentHeight, 27 | ) 28 | useVariableSpacing: false 29 | 30 | Keys.onReturnPressed: popup.keyboardAccept() 31 | Keys.onEnterPressed: popup.keyboardAccept() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/gui/Popups/HFlickableColumnPopup.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "../Base" 6 | 7 | HPopup { 8 | id: popup 9 | 10 | default property alias pageData: page.columnData 11 | 12 | readonly property alias page: page 13 | 14 | signal keyboardAccept() 15 | 16 | HFlickableColumnPage { 17 | id: page 18 | implicitWidth: Math.min( 19 | popup.maximumPreferredWidth, 20 | theme.controls.popup.defaultWidth, 21 | ) 22 | implicitHeight: Math.min( 23 | popup.maximumPreferredHeight, 24 | implicitHeaderHeight + implicitFooterHeight + contentHeight, 25 | ) 26 | flickShortcuts.disableIfAnyPopupOrMenu: false 27 | 28 | Keys.onReturnPressed: popup.keyboardAccept() 29 | Keys.onEnterPressed: popup.keyboardAccept() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/gui/Popups/HPopupShortcut.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import "../Base" 5 | 6 | HShortcut { 7 | enabled: active 8 | 9 | onSequencesChanged: check() 10 | onSequenceChanged: check() 11 | 12 | function check() { 13 | if (sequences.includes("Escape") || sequence === "Escape") 14 | console.warn( 15 | qsTr("%1: assigning Escape to a popup action causes conflicts") 16 | .arg(sequence || JSON.stringify(sequences)) 17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/gui/Popups/InvalidAccessTokenPopup.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "../Base" 6 | import "../Base/Buttons" 7 | 8 | HFlickableColumnPopup { 9 | id: popup 10 | 11 | property string userId 12 | 13 | function addAccount() { 14 | window.mainUI.pageLoader.show("Pages/AddAccount/AddAccount.qml") 15 | } 16 | 17 | page.footer: AutoDirectionLayout { 18 | ApplyButton { 19 | id: signBackButton 20 | text: qsTr("Sign back in") 21 | icon.name: "sign-back-in" 22 | onClicked: { 23 | addAccount() 24 | popup.close() 25 | } 26 | } 27 | 28 | CancelButton { 29 | text: qsTr("Close") 30 | onClicked: popup.close() 31 | } 32 | } 33 | 34 | onClosed: if ( 35 | window.uiState.pageProperties.userId === userId || 36 | (window.uiState.pageProperties.userRoomId || [])[0] === userId 37 | ) addAccount() 38 | 39 | SummaryLabel { 40 | text: qsTr("Signed out from %1").arg(coloredNameHtml("", userId)) 41 | textFormat: SummaryLabel.StyledText 42 | } 43 | 44 | DetailsLabel { 45 | text: qsTr( 46 | "You have been disconnected from another session, " + 47 | "by the server for security reasons, or the access token in " + 48 | "your configuration file is invalid." 49 | ) 50 | } 51 | 52 | onOpened: signBackButton.forceActiveFocus() 53 | } 54 | -------------------------------------------------------------------------------- /src/gui/Popups/PushRuleSettingsPopup/ContentRule.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../Base" 7 | import "../../Base/Buttons" 8 | 9 | HColumnLayout { 10 | readonly property alias idField: idField 11 | 12 | HLabeledItem { 13 | label.text: qsTr("Word or glob pattern to match:") 14 | Layout.fillWidth: true 15 | 16 | HTextField { 17 | id: idField 18 | width: parent.width 19 | defaultText: rule.kind === "content" ? rule.pattern : "" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/gui/Popups/PushRuleSettingsPopup/CustomLabel.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../Base" 7 | 8 | HLabel { 9 | opacity: enabled ? 1 : theme.disabledElementsOpacity 10 | wrapMode: HLabel.Wrap 11 | Layout.fillWidth: true 12 | 13 | Behavior on opacity { HNumberAnimation {} } 14 | } 15 | -------------------------------------------------------------------------------- /src/gui/Popups/PushRuleSettingsPopup/PushConditions/CustomFlow.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "../../../Base" 6 | 7 | HFlow { 8 | spacing: theme.spacing / 2 9 | 10 | // transitions break CustomLabel opacity for some reason 11 | populate: null 12 | add: null 13 | move: null 14 | } 15 | -------------------------------------------------------------------------------- /src/gui/Popups/PushRuleSettingsPopup/PushConditions/PushContainsDisplayName.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import ".." 6 | import "../../../Base" 7 | 8 | CustomLabel { 9 | readonly property var matrixObject: ({kind: model.kind}) 10 | 11 | text: qsTr("Message contains my display name") 12 | } 13 | -------------------------------------------------------------------------------- /src/gui/Popups/PushRuleSettingsPopup/PushConditions/PushRoomMemberCount.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import ".." 7 | import "../../../Base" 8 | 9 | CustomFlow { 10 | readonly property var matrixObject: ({ 11 | kind: model.kind, 12 | is: operatorCombo.operators[operatorCombo.currentIndex] 13 | .replace("==", "") + countSpin.value, 14 | }) 15 | 16 | CustomLabel { 17 | text: qsTr("Room has") 18 | verticalAlignment: CustomLabel.AlignVCenter 19 | height: operatorCombo.height 20 | } 21 | 22 | HComboBox { 23 | readonly property var operators: ["==", ">=", "<=", ">", "<"] 24 | 25 | id: operatorCombo 26 | width: Math.min(implicitWidth, parent.width) 27 | currentIndex: operators.indexOf(/[=<>]+/.exec(condition.is + "==")[0]) 28 | model: [ 29 | qsTr("exactly"), 30 | qsTr("at least"), 31 | qsTr("at most"), 32 | qsTr("more than"), 33 | qsTr("less than"), 34 | ] 35 | } 36 | 37 | HSpinBox { 38 | id: countSpin 39 | width: Math.min(implicitWidth, parent.width) 40 | defaultValue: parseInt(condition.is.replace(/[=<>]/, ""), 10) 41 | } 42 | 43 | CustomLabel { 44 | text: qsTr("members") 45 | verticalAlignment: CustomLabel.AlignVCenter 46 | height: operatorCombo.height 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/gui/Popups/PushRuleSettingsPopup/PushConditions/PushSenderNotificationPermission.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import ".." 7 | import "../../../Base" 8 | 9 | CustomFlow { 10 | readonly property var matrixObject: ({ 11 | kind: model.kind, 12 | key: keyCombo.editText, 13 | }) 14 | 15 | CustomLabel { 16 | text: qsTr("Sender has permission to send") 17 | verticalAlignment: CustomLabel.AlignVCenter 18 | height: keyCombo.height 19 | } 20 | 21 | HComboBox { 22 | id: keyCombo 23 | width: Math.min(implicitWidth, parent.width) 24 | editable: true 25 | editText: condition.key 26 | currentIndex: model.indexOf(condition.key) 27 | model: [...new Set(["room", condition.key])].sort() 28 | } 29 | 30 | CustomLabel { 31 | text: qsTr("notifications") 32 | verticalAlignment: CustomLabel.AlignVCenter 33 | height: keyCombo.height 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/gui/Popups/PushRuleSettingsPopup/PushConditions/PushUnknownCondition.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import ".." 7 | import "../../../Base" 8 | 9 | AutoDirectionLayout { 10 | readonly property var matrixObject: { 11 | try { 12 | JSON.parse(jsonField.text) 13 | } catch (e) { 14 | // TODO 15 | return condition.condition 16 | } 17 | } 18 | 19 | rowSpacing: theme.spacing / 2 20 | columnSpacing: rowSpacing 21 | 22 | CustomLabel { 23 | text: qsTr("Custom JSON:") 24 | verticalAlignment: CustomLabel.AlignVCenter 25 | Layout.fillWidth: false 26 | Layout.fillHeight: true 27 | } 28 | 29 | HTextField { 30 | // TODO: validate the JSON 31 | id: jsonField 32 | font.family: theme.fontFamily.mono 33 | defaultText: JSON.stringify(condition.condition) 34 | Layout.fillWidth: true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/gui/Popups/PushRuleSettingsPopup/RoomRule.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../Base" 7 | import "../../Base/Buttons" 8 | 9 | HColumnLayout { 10 | readonly property alias idField: idField 11 | 12 | HLabeledItem { 13 | label.text: qsTr("Room ID:") 14 | Layout.fillWidth: true 15 | 16 | HTextField { 17 | id: idField 18 | width: parent.width 19 | defaultText: rule.kind === "room" ? rule.rule_id : "" 20 | placeholderText: qsTr("!room:example.org") 21 | maximumLength: 255 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/gui/Popups/PushRuleSettingsPopup/SenderRule.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../../Base" 7 | import "../../Base/Buttons" 8 | 9 | HColumnLayout { 10 | readonly property alias idField: idField 11 | 12 | HLabeledItem { 13 | label.text: qsTr("User ID:") 14 | Layout.fillWidth: true 15 | 16 | HTextField { 17 | id: idField 18 | width: parent.width 19 | defaultText: rule.kind === "sender" ? rule.rule_id : "" 20 | placeholderText: qsTr("@alice:example.org") 21 | maximumLength: 255 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/gui/Popups/SummaryLabel.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Layouts 1.12 6 | import "../Base" 7 | 8 | HLabel { 9 | wrapMode: HLabel.Wrap 10 | font.bold: true 11 | visible: Boolean(text) 12 | 13 | Layout.fillWidth: true 14 | } 15 | -------------------------------------------------------------------------------- /src/gui/PythonBridge/Globals.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | pragma Singleton 5 | import QtQuick 2.12 6 | 7 | QtObject { 8 | readonly property var pendingCoroutines: ({}) 9 | readonly property var hideErrorTypes: new Set([ 10 | "gaierror", "SSLError", "MatrixInvalidAccessToken", 11 | ]) 12 | } 13 | -------------------------------------------------------------------------------- /src/gui/PythonBridge/PythonRootBridge.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import CppUtils 0.1 6 | import "." 7 | 8 | PythonBridge { 9 | property bool ready: false 10 | property bool startupAnyAccountsSaved: false 11 | 12 | readonly property EventHandlers eventHandlers: EventHandlers {} 13 | 14 | Component.onCompleted: { 15 | for (var func in eventHandlers) { 16 | if (! eventHandlers.hasOwnProperty(func)) continue 17 | if (! func.startsWith("on")) continue 18 | setHandler(func.replace(/^on/, ""), eventHandlers[func]) 19 | } 20 | 21 | addImportPath("src") 22 | addImportPath("qrc:/src") 23 | 24 | importNames("backend.qml_bridge", ["BRIDGE"], () => { 25 | callCoro("get_settings", [], ([settings, state, hist, theme, themeRules]) => { 26 | CppUtils.setProxy(settings.General.proxy || "") 27 | 28 | window.settings = settings 29 | window.uiState = state 30 | window.history = hist 31 | window.theme = Qt.createQmlObject(theme, window, "theme") 32 | utils.theme = window.theme 33 | window.themeRules = themeRules 34 | 35 | callCoro("saved_accounts.any_saved", [], any => { 36 | if (any) { callCoro("load_saved_accounts", []) } 37 | 38 | startupAnyAccountsSaved = any 39 | ready = true 40 | }) 41 | }) 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/gui/PythonBridge/qmldir: -------------------------------------------------------------------------------- 1 | singleton Globals 0.1 Globals.qml 2 | -------------------------------------------------------------------------------- /src/gui/ShortcutBundles/TabShortcuts.qml: -------------------------------------------------------------------------------- 1 | // Copyright Mirage authors & contributors 2 | // SPDX-License-Identifier: LGPL-3.0-or-later 3 | 4 | import QtQuick 2.12 5 | import "../Base" 6 | 7 | HQtObject { 8 | id: root 9 | 10 | property Item container: parent 11 | property bool active: container.count > 1 12 | property bool disableIfAnyPopupOrMenu: true 13 | 14 | HShortcut { 15 | active: root.active 16 | disableIfAnyPopupOrMenu: root.disableIfAnyPopupOrMenu 17 | sequences: window.settings.Keys.previous_tab 18 | onActivated: container.setCurrentIndex( 19 | utils.numberWrapAt(container.currentIndex - 1, container.count), 20 | ) 21 | } 22 | 23 | HShortcut { 24 | active: root.active 25 | disableIfAnyPopupOrMenu: root.disableIfAnyPopupOrMenu 26 | sequences: window.settings.Keys.next_tab 27 | onActivated: container.setCurrentIndex( 28 | utils.numberWrapAt(container.currentIndex + 1, container.count), 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/gui/qmldir: -------------------------------------------------------------------------------- 1 | singleton ModelStore 0.1 ModelStore.qml 2 | singleton ArgumentParser 0.1 ArgumentParser.qml 3 | -------------------------------------------------------------------------------- /src/icons/thin/account-settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/add-account.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/icons/thin/add-chat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/apply.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/bookmark-add.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/thin/bookmark-remove.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/thin/broken-image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/icons/thin/cancel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/check-mark-partial.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/check-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/clear-messages.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/close-view.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/icons/thin/combo-box-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/combo-box-open.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/confirm-uploading-file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/copy-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/copy-local-path.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/icons/thin/copy-room-id.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/copy-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/copy-user-id.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/cut-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/debug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/deselect-all-messages.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/developer-console.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/device-action-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/icons/thin/device-blacklisted.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/device-current.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/device-delete-checked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/device-delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/device-ignored.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/device-refresh-list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/device-rename.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/device-unset.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/device-verified.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/device-verify.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/documentation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/downloading.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/thin/drop-file-upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/every-room.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/expand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/export-keys.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/feature-unavailable-offline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/field-help.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/thin/go-back-to-chat-from-main-pane.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/go-back-to-chat-from-room-pane.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/go-back-to-main-pane.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/go-to-room-pane.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/ignore-user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/image-alt-scale-mode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/icons/thin/image-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/image-fullscreen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/image-pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/icons/thin/image-play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/image-rotate-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/image-rotate-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/image-speed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/import-keys.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/invite-accept.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/invite-decline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/invite-received.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/join.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/menu-add-chat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/thin/menu-item-check-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/notifications-enable.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/notifications-highlights-only.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/notifications-mute.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/ok.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/open-externally.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/paste-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/phone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/player-fullscreen-exit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/player-fullscreen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/player-loop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/player-pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/icons/thin/player-play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/player-restart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/player-speed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/player-track-audio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/player-track-subtitle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/player-track-video.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/player-volume-high.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/icons/thin/player-volume-low.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/icons/thin/player-volume-mute.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/icons/thin/presence-busy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/presence-invisible.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/icons/thin/presence-offline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/thin/presence-online.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/previously-set-status.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/pushrule-action-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/pushrule-action-bubble.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/pushrule-action-sound.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/icons/thin/pushrule-action-urgency-hint.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/pushrule-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/pushrule-condition-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/pushrule-condition-remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/pushrule-edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/icons/thin/pushrule-remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/redo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/reduced-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/reduced-room-buttons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/register.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/reload-config-files.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/remove-message.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/reply-cancel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/reply-to.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/report-error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/reset-password.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/retry.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-ban.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-create.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-forget.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-header-copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-header-deselect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-header-remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-join.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-kick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/icons/thin/room-leave.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/icons/thin/room-menu-notifications.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-pane-expand-search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-pin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-send-invite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-unpin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-view-files.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-view-history.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-view-members.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-view-notifications.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/room-view-settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/select-all-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/select-until-here.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/server-connect-to-address.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/server-ping-bad.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/thin/server-ping-fail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/server-ping-good.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/server-ping-medium.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/thin/server-visit-website.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/thin/set-status.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/sign-back-in.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/sign-in-insecure.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/sign-in-local-http.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/sign-in-secure.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/sign-in.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/sign-out.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/start-direct-chat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/status.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/stop-ignore-user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/submenu-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/theme.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/toggle-select-message.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/transfer-cancel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/transfer-pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/icons/thin/transfer-resume.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/tray-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/src/icons/thin/tray-icon.png -------------------------------------------------------------------------------- /src/icons/thin/typing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/undo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/unknown-devices-inspect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/unknown-devices-warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/upload-avatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/upload-file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/uploading.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/thin/user-invited.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/user-power-100.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/user-power-50.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/user-power-default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/thin/username.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/midnight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/src/images/midnight.jpg -------------------------------------------------------------------------------- /src/sounds/default.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirukana/mirage/9a4ababd9aa59bd78ad239422918c2c4eaaf6b7d/src/sounds/default.wav --------------------------------------------------------------------------------