Merge branch 'dev' into 'main'
Merge version 0.7.3 See merge request mx-moment/moment!6
							
								
								
									
										34
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,34 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
name: Bug report
 | 
			
		||||
about: 'Report an unexpected behavior '
 | 
			
		||||
title: ''
 | 
			
		||||
labels: bug
 | 
			
		||||
assignees: ''
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### Description
 | 
			
		||||
 | 
			
		||||
Describe your issue in details, provide error logs or screenshots if possible.
 | 
			
		||||
 | 
			
		||||
### Your environment
 | 
			
		||||
 | 
			
		||||
- OS or distribution (e.g. Arch Linux, macOS 10.15, Windows 7...)
 | 
			
		||||
- Architecture (e.g. x86 64bit)
 | 
			
		||||
- For Linux users: your desktop environment or window manager (e.g. GNOME 3.34 Wayland, i3 4.17, etc)
 | 
			
		||||
- How did you install Mirage? (e.g. manual build, distribution repository, AppImage, Flatpak...)
 | 
			
		||||
- For manual installations: your Qt version
 | 
			
		||||
- For manual installations: your Python version
 | 
			
		||||
 | 
			
		||||
### Steps to reproduce
 | 
			
		||||
 | 
			
		||||
1. Do this...
 | 
			
		||||
2. Do that...
 | 
			
		||||
 | 
			
		||||
### Expected behavior
 | 
			
		||||
 | 
			
		||||
Tell us what should happen
 | 
			
		||||
 | 
			
		||||
### Actual behavior
 | 
			
		||||
 | 
			
		||||
Tell us what happens instead
 | 
			
		||||
							
								
								
									
										10
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,10 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
name: Feature request
 | 
			
		||||
about: Suggest an idea for the application or project
 | 
			
		||||
title: ''
 | 
			
		||||
labels: enhancement
 | 
			
		||||
assignees: ''
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										10
									
								
								.github/ISSUE_TEMPLATE/question.md
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,10 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
name: Question
 | 
			
		||||
about: Ask a question about the application or project
 | 
			
		||||
title: ''
 | 
			
		||||
labels: question
 | 
			
		||||
assignees: ''
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -16,6 +16,8 @@ dist
 | 
			
		||||
Makefile
 | 
			
		||||
mirage
 | 
			
		||||
mirage.pro.user
 | 
			
		||||
moment
 | 
			
		||||
moment.pro.user
 | 
			
		||||
*.AppImage
 | 
			
		||||
 | 
			
		||||
tags
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -1,5 +1,4 @@
 | 
			
		||||
# Moment
 | 
			
		||||
https://img.shields.io/gitlab/v/release/mx-moment/moment
 | 
			
		||||
[](https://gitlab.com/mx-moment/moment/-/releases)
 | 
			
		||||
[](https://github.com/poljar/matrix-nio)
 | 
			
		||||
[](https://matrix.to/#/#moment-client:matrix.org)
 | 
			
		||||
@@ -20,7 +19,7 @@ Written in Qt/QML and Python, **currently in alpha**.
 | 
			
		||||
Moment is based on [Mirage](https://github.com/mirukana/mirage),
 | 
			
		||||
see differences [here](docs/MIRAGEDIFF.md).
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Currently Implemented Features
 | 
			
		||||
 | 
			
		||||
@@ -96,10 +95,10 @@ see differences [here](docs/MIRAGEDIFF.md).
 | 
			
		||||
 | 
			
		||||
## Screenshots
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ The format is based on
 | 
			
		||||
and this project adheres to
 | 
			
		||||
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 | 
			
		||||
 | 
			
		||||
- [0.7.3 (2022-01-24)](#073-2022-01-24)
 | 
			
		||||
- [0.7.2 (2021-07-26)](#072-2021-07-26)
 | 
			
		||||
- [0.7.1 (2021-03-04)](#071-2021-03-04)
 | 
			
		||||
- [0.7.0 (2021-02-28)](#070-2021-02-28)
 | 
			
		||||
@@ -23,6 +24,44 @@ and this project adheres to
 | 
			
		||||
- [0.4.0 (2020-03-21)](#040-2020-03-21)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 0.7.3 (2022-01-24)
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
  - [Keybindings page](KEYBINDINGS.md)
 | 
			
		||||
 | 
			
		||||
  - When first starting Moment, offer to migrate config
 | 
			
		||||
 | 
			
		||||
  - Foliage theme
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
  - Renamed Mirage to Moment
 | 
			
		||||
 | 
			
		||||
  - Default config, cache and data directories
 | 
			
		||||
 | 
			
		||||
  - Default keybinds:
 | 
			
		||||
    - quit is now Ctrl+Q
 | 
			
		||||
    - reply is now Ctrl+R
 | 
			
		||||
    - remove is now Ctrl+Shift+R
 | 
			
		||||
    - "focus filter" is now Ctrl+K
 | 
			
		||||
    - "previous message" is now Ctrl+I
 | 
			
		||||
 | 
			
		||||
  - In-app links lead to
 | 
			
		||||
   [the Moment repository](https://gitlab.com/mx-moment/moment)
 | 
			
		||||
 | 
			
		||||
  - Default theme is now Foliage.qpl
 | 
			
		||||
 | 
			
		||||
### Removed
 | 
			
		||||
 | 
			
		||||
- Appimage: remove Appimage installation method
 | 
			
		||||
 | 
			
		||||
### Fixed
 | 
			
		||||
 | 
			
		||||
  - Compatibility with Python 3.10
 | 
			
		||||
 | 
			
		||||
  - Minor UI fixes
 | 
			
		||||
 | 
			
		||||
## 0.7.2 (2021-07-26)
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ By sending your changes, you agree to license them under the LGPL 3.0 or later.
 | 
			
		||||
 | 
			
		||||
### Procedure
 | 
			
		||||
 | 
			
		||||
Start by forking the main repository from GitHub, then
 | 
			
		||||
Start by forking the main repository from GitLab, then
 | 
			
		||||
clone your fork and switch to a new branch based on `dev`, in which 
 | 
			
		||||
you will make your changes:
 | 
			
		||||
 | 
			
		||||
@@ -62,7 +62,7 @@ You will then be able to make a pull request by going
 | 
			
		||||
to the [main repo](https://gitlab.com/mx-moment/moment).
 | 
			
		||||
 | 
			
		||||
Once your pull request is merged, you can update `dev`, and delete your
 | 
			
		||||
GitHub and local branch:
 | 
			
		||||
GitLab and local branch:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
git fetch upstream
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ but compiling on Windows and macOS should be possible with the right tools.
 | 
			
		||||
 | 
			
		||||
- [Packages](#packages)
 | 
			
		||||
  - [Linux](#linux)
 | 
			
		||||
    - [AppImage](#appimage)
 | 
			
		||||
    - [Flatpak](#flatpak)
 | 
			
		||||
    - [Alpine Linux / postmarketOS](#alpine-linux--postmarketOS)
 | 
			
		||||
    - [Arch Linux](#arch-linux)
 | 
			
		||||
@@ -37,22 +36,9 @@ but compiling on Windows and macOS should be possible with the right tools.
 | 
			
		||||
 | 
			
		||||
For developement, or if none of the package options are satisfying, 
 | 
			
		||||
see [manual installation](#manual-installation).  
 | 
			
		||||
Packages other than the AppImage and Flatpak are not maintained by the Mirage 
 | 
			
		||||
Packages other than the Flatpak are not maintained by the Mirage 
 | 
			
		||||
authors, and thus might be outdated.
 | 
			
		||||
 | 
			
		||||
#### AppImage (Leftover instructions from Mirage; not supported for Moment)
 | 
			
		||||
 | 
			
		||||
For **x86 64bit glibc-based systems**, Mirage is available as an AppImage
 | 
			
		||||
on the [release page](https://github.com/mirukana/mirage/releases).
 | 
			
		||||
 | 
			
		||||
AppImages are single executable files that contain the app and all 
 | 
			
		||||
its dependencies.  
 | 
			
		||||
Mirage images are built in Ubuntu 16.04, and should therefore run on any distro
 | 
			
		||||
released in April 2016 or later.
 | 
			
		||||
 | 
			
		||||
[How to start AppImages](https://docs.appimage.org/introduction/quickstart.html#how-to-run-an-appimage)
 | 
			
		||||
(TL;DR: `chmod +x Mirage-*.AppImage && ./Mirage-*.AppImage`)
 | 
			
		||||
 | 
			
		||||
#### Flatpak (Leftover instructions from Mirage; not supported for Moment)
 | 
			
		||||
 | 
			
		||||
Mirage is also available as a Flatpak.
 | 
			
		||||
 
 | 
			
		||||
@@ -6,114 +6,114 @@ Keybindings as defined in [the default configuration file](src/config/settings.p
 | 
			
		||||
 | 
			
		||||
Key | Function
 | 
			
		||||
------ | ------
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>C</kbd> | Toggle compact interface
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>+</kbd> | Zoom in
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>-</kbd> | Zoom out
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>=</kbd> | Reset zoom
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Left</kbd> <br> <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>H</kbd> | Previous tab
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Right</kbd> <br> <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>L</kbd> | Next tab
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Tab</kbd> | Switch to the last opened page
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>H</kbd> | Earlier page in history (page back)
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>L</kbd> | Later page in history (page forward)
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>H</kbd> | Toggle notifications, except highlights
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>N</kbd> | Toggle notifications
 | 
			
		||||
<kbd>Ctrl + Alt + C</kbd> | Toggle compact interface
 | 
			
		||||
<kbd>Ctrl + +</kbd> | Zoom in
 | 
			
		||||
<kbd>Ctrl + -</kbd> | Zoom out
 | 
			
		||||
<kbd>Ctrl + =</kbd> | Reset zoom
 | 
			
		||||
<kbd>Alt + Shift + Left</kbd> <br> <kbd>Alt + Shift + H</kbd> | Previous tab
 | 
			
		||||
<kbd>Alt + Shift + Right</kbd> <br> <kbd>Alt + Shift + L</kbd> | Next tab
 | 
			
		||||
<kbd>Ctrl + Tab</kbd> | Switch to the last opened page
 | 
			
		||||
<kbd>Ctrl + H</kbd> | Earlier page in history (page back)
 | 
			
		||||
<kbd>Ctrl + L</kbd> | Later page in history (page forward)
 | 
			
		||||
<kbd>Ctrl + Alt + H</kbd> | Toggle notifications, except highlights
 | 
			
		||||
<kbd>Ctrl + Alt + N</kbd> | Toggle notifications
 | 
			
		||||
<kbd>F1</kbd> | QML developer console
 | 
			
		||||
<kbd>Shift</kbd> + <kbd>F1</kbd> | Python debugger
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>F1</kbd> | Python remote debugger
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Q</kbd> | Quit Moment *
 | 
			
		||||
<kbd>Shift + F1</kbd> | Python debugger
 | 
			
		||||
<kbd>Alt + F1</kbd> | Python remote debugger
 | 
			
		||||
<kbd>Ctrl + Q</kbd> | Quit Moment *
 | 
			
		||||
 | 
			
		||||
## Scrolling bindings
 | 
			
		||||
 | 
			
		||||
Key | Function
 | 
			
		||||
------ | ------
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Up</kbd> <br> <kbd>Alt</kbd> + <kbd>K</kbd> | Scroll up
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Down</kbd> <br> <kbd>Alt</kbd> + <kbd>J</kbd> | Scroll down
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Up</kbd> <br> <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>K</kbd> <br> <kbd>PgUp</kbd> | Page up
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Down</kbd> <br> <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>J</kbd> <br> <kbd>PgDown</kbd> | Page down
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Up</kbd> <br> <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>K</kbd> <br> <kbd>Home</kbd> | Scroll to top
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Down</kbd> <br> <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>J</kbd> <br> <kbd>End</kbd> | Scroll to bottom
 | 
			
		||||
<kbd>Alt + Up</kbd> <br> <kbd>Alt + K</kbd> | Scroll up
 | 
			
		||||
<kbd>Alt + Down</kbd> <br> <kbd>Alt + J</kbd> | Scroll down
 | 
			
		||||
<kbd>Ctrl + Alt + Up</kbd> <br> <kbd>Ctrl + Alt + K</kbd> <br> <kbd>PgUp</kbd> | Page up
 | 
			
		||||
<kbd>Ctrl + Alt + Down</kbd> <br> <kbd>Ctrl + Alt + J</kbd> <br> <kbd>PgDown</kbd> | Page down
 | 
			
		||||
<kbd>Ctrl + Alt + Shift + Up</kbd> <br> <kbd>Ctrl + Alt + Shift + K</kbd> <br> <kbd>Home</kbd> | Scroll to top
 | 
			
		||||
<kbd>Ctrl + Alt + Shift + Down</kbd> <br> <kbd>Ctrl + Alt + Shift + J</kbd> <br> <kbd>End</kbd> | Scroll to bottom
 | 
			
		||||
 | 
			
		||||
## Account bindings
 | 
			
		||||
 | 
			
		||||
Key | Function
 | 
			
		||||
------ | ------
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd> | Add new account
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>O</kbd> | Collapse current account
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>A</kbd> | Current account settings
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>P</kbd> | Current account context menu
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>U</kbd> <br> <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>A</kbd> | Unavailable status
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>I</kbd> | Invisible status
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>O</kbd> | Offline status
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>N</kbd> | Previous account
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>N</kbd> | Next account
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>1</kbd> | Switch to account 1
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>2</kbd> | Switch to account 2
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>3</kbd> | Switch to account 3
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>4</kbd> | Switch to account 4
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>5</kbd> | Switch to account 5
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>6</kbd> | Switch to account 6
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>7</kbd> | Switch to account 7
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>8</kbd> | Switch to account 8
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>9</kbd> | Switch to account 9
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>0</kbd> | Switch to account 10
 | 
			
		||||
<kbd>Alt + Shift + A</kbd> | Add new account
 | 
			
		||||
<kbd>Alt + O</kbd> | Collapse current account
 | 
			
		||||
<kbd>Alt + A</kbd> | Current account settings
 | 
			
		||||
<kbd>Alt + P</kbd> | Current account context menu
 | 
			
		||||
<kbd>Ctrl + Alt + U</kbd> <br> <kbd>Ctrl + Alt + A</kbd> | Unavailable status
 | 
			
		||||
<kbd>Ctrl + Alt + I</kbd> | Invisible status
 | 
			
		||||
<kbd>Ctrl + Alt + O</kbd> | Offline status
 | 
			
		||||
<kbd>Alt + Shift + N</kbd> | Previous account
 | 
			
		||||
<kbd>Alt + N</kbd> | Next account
 | 
			
		||||
<kbd>Ctrl + 1</kbd> | Switch to account 1
 | 
			
		||||
<kbd>Ctrl + 2</kbd> | Switch to account 2
 | 
			
		||||
<kbd>Ctrl + 3</kbd> | Switch to account 3
 | 
			
		||||
<kbd>Ctrl + 4</kbd> | Switch to account 4
 | 
			
		||||
<kbd>Ctrl + 5</kbd> | Switch to account 5
 | 
			
		||||
<kbd>Ctrl + 6</kbd> | Switch to account 6
 | 
			
		||||
<kbd>Ctrl + 7</kbd> | Switch to account 7
 | 
			
		||||
<kbd>Ctrl + 8</kbd> | Switch to account 8
 | 
			
		||||
<kbd>Ctrl + 9</kbd> | Switch to account 9
 | 
			
		||||
<kbd>Ctrl + 0</kbd> | Switch to account 10
 | 
			
		||||
 | 
			
		||||
## Room bindings
 | 
			
		||||
 | 
			
		||||
Key | Function
 | 
			
		||||
------ | ------
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>C</kbd> | Create a new room (start chat)
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>F</kbd> | Focus filter
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>F</kbd> | Clear filter
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Up</kbd> <br> <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>K</kbd> | Previous room
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Down</kbd> <br> <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>J</kbd> | Next room
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>U</kbd> | Previous unread
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>U</kbd> | Next unread
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>U</kbd> | Oldest unread
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>U</kbd> | Latest unread
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd> | Previous highlight
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>M</kbd> | Next highlight
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd> | Oldest highlight
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>M</kbd> | Latest highlight
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>1</kbd> | Room number 1 in account
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>2</kbd> | Room number 2 in account
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>3</kbd> | Room number 3 in account
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>4</kbd> | Room number 4 in account
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>5</kbd> | Room number 5 in account
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>6</kbd> | Room number 6 in account
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>7</kbd> | Room number 7 in account
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>8</kbd> | Room number 8 in account
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>9</kbd> | Room number 9 in account
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>0</kbd> | Room number 10 in account
 | 
			
		||||
<kbd>Alt + C</kbd> | Create a new room (start chat)
 | 
			
		||||
<kbd>Alt + F</kbd> <br> <kbd>Ctrl + K</kbd> | Focus filter *
 | 
			
		||||
<kbd>Alt + Shift + F</kbd> | Clear filter
 | 
			
		||||
<kbd>Alt + Shift + Up</kbd> <br> <kbd>Alt + Shift + K</kbd> | Previous room
 | 
			
		||||
<kbd>Alt + Shift + Down</kbd> <br> <kbd>Alt + Shift + J</kbd> | Next room
 | 
			
		||||
<kbd>Alt + Shift + U</kbd> | Previous unread
 | 
			
		||||
<kbd>Alt + U</kbd> | Next unread
 | 
			
		||||
<kbd>Ctrl + Shift + U</kbd> | Oldest unread
 | 
			
		||||
<kbd>Ctrl + U</kbd> | Latest unread
 | 
			
		||||
<kbd>Alt + Shift + M</kbd> | Previous highlight
 | 
			
		||||
<kbd>Alt + M</kbd> | Next highlight
 | 
			
		||||
<kbd>Ctrl + Shift + M</kbd> | Oldest highlight
 | 
			
		||||
<kbd>Ctrl + M</kbd> | Latest highlight
 | 
			
		||||
<kbd>Alt + 1</kbd> | Room number 1 in account
 | 
			
		||||
<kbd>Alt + 2</kbd> | Room number 2 in account
 | 
			
		||||
<kbd>Alt + 3</kbd> | Room number 3 in account
 | 
			
		||||
<kbd>Alt + 4</kbd> | Room number 4 in account
 | 
			
		||||
<kbd>Alt + 5</kbd> | Room number 5 in account
 | 
			
		||||
<kbd>Alt + 6</kbd> | Room number 6 in account
 | 
			
		||||
<kbd>Alt + 7</kbd> | Room number 7 in account
 | 
			
		||||
<kbd>Alt + 8</kbd> | Room number 8 in account
 | 
			
		||||
<kbd>Alt + 9</kbd> | Room number 9 in account
 | 
			
		||||
<kbd>Alt + 0</kbd> | Room number 10 in account
 | 
			
		||||
(no binding) | Jump to specific room by ID
 | 
			
		||||
 | 
			
		||||
## Chat bindings
 | 
			
		||||
 | 
			
		||||
Key | Function
 | 
			
		||||
------ | ------
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>R</kbd> | Focus room pane
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>R</kbd> | Hide room pane
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>I</kbd> | Invite members
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Escape</kbd> | Leave current chat
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>S</kbd> | Upload file
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd> | Send file at clipboard path
 | 
			
		||||
<kbd>Alt + R</kbd> | Focus room pane
 | 
			
		||||
<kbd>Ctrl + Alt + R</kbd> | Hide room pane
 | 
			
		||||
<kbd>Alt + I</kbd> | Invite members
 | 
			
		||||
<kbd>Alt + Escape</kbd> | Leave current chat
 | 
			
		||||
<kbd>Alt + S</kbd> | Upload file
 | 
			
		||||
<kbd>Alt + Shift + S</kbd> | Send file at clipboard path
 | 
			
		||||
 | 
			
		||||
## Message bindings
 | 
			
		||||
 | 
			
		||||
Key | Function
 | 
			
		||||
------ | ------
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Up</kbd> <br> <kbd>Ctrl</kbd> + <kbd>K</kbd> | Focus previous message
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Down</kbd> <br> <kbd>Ctrl</kbd> + <kbd>J</kbd> | Focus next message
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Space</kbd> | Select focused message
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Space</kbd> | Select messages until here
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>D</kbd> | Unfocus or deselect
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>S</kbd> | Display seen tooltips
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>R</kbd> <br> <kbd>Alt</kbd> + <kbd>Del</kbd> | Remove message
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Q</kbd> | Reply
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>D</kbd> | Debug message
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>O</kbd> | Open link/file in message
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>O</kbd> | Open link/file externally
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>C</kbd> | Copy downloaded file path
 | 
			
		||||
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>L</kbd> | Clear messages
 | 
			
		||||
<kbd>Ctrl + Up</kbd> <br> <kbd>Ctrl + I</kbd> | Focus previous message *
 | 
			
		||||
<kbd>Ctrl + Down</kbd> <br> <kbd>Ctrl + J</kbd> | Focus next message
 | 
			
		||||
<kbd>Ctrl + Space</kbd> | Select focused message
 | 
			
		||||
<kbd>Ctrl + Shift + Space</kbd> | Select messages until here
 | 
			
		||||
<kbd>Ctrl + D</kbd> | Unfocus or deselect
 | 
			
		||||
<kbd>Ctrl + S</kbd> | Display seen tooltips
 | 
			
		||||
<kbd>Ctrl + Shift + R</kbd> <br> <kbd>Alt + Del</kbd> | Remove message *
 | 
			
		||||
<kbd>Ctrl + R</kbd> | Reply *
 | 
			
		||||
<kbd>Ctrl + Shift + D</kbd> | Debug message
 | 
			
		||||
<kbd>Ctrl + O</kbd> | Open link/file in message
 | 
			
		||||
<kbd>Ctrl + Shift + O</kbd> | Open link/file externally
 | 
			
		||||
<kbd>Ctrl + Shift + C</kbd> | Copy downloaded file path
 | 
			
		||||
<kbd>Ctrl + Shift + L</kbd> | Clear messages
 | 
			
		||||
 | 
			
		||||
## Image viewer bindings
 | 
			
		||||
 | 
			
		||||
@@ -121,20 +121,20 @@ Key | Function
 | 
			
		||||
------ | ------
 | 
			
		||||
<kbd>X</kbd> <br> <kbd>Q</kbd> | Close image viewer
 | 
			
		||||
<kbd>E</kbd> | Expand image viewer
 | 
			
		||||
<kbd>F</kbd> <br> <kbd>F11</kbd> <br> <kbd>Alt</kbd> + <kbd>Return</kbd> <br> <kbd>Alt</kbd> + <kbd>Enter</kbd> | Fullscreen image viewer
 | 
			
		||||
<kbd>H</kbd> <br> <kbd>Left</kbd> <br> <kbd>Alt</kbd> + <kbd>H</kbd> <br> <kbd>Alt</kbd> + <kbd>Left</kbd> | Pan image left
 | 
			
		||||
<kbd>J</kbd> <br> <kbd>Down</kbd> <br> <kbd>Alt</kbd> + <kbd>J</kbd> <br> <kbd>Alt</kbd> + <kbd>Down</kbd> | Pan image down
 | 
			
		||||
<kbd>K</kbd> <br> <kbd>Up</kbd> <br> <kbd>Alt</kbd> + <kbd>K</kbd> <br> <kbd>Alt</kbd> + <kbd>Up</kbd> | Pan image up
 | 
			
		||||
<kbd>L</kbd> <br> <kbd>Right</kbd> <br> <kbd>Alt</kbd> + <kbd>L</kbd> <br> <kbd>Alt</kbd> + <kbd>Right</kbd> | Pan image right
 | 
			
		||||
<kbd>Z</kbd> <br> <kbd>+</kbd> <br> <kbd>Ctrl</kbd> + <kbd>+</kbd> | Zoom in
 | 
			
		||||
<kbd>Shift</kbd> + <kbd>Z</kbd> <br> <kbd>-</kbd> <br> <kbd>Ctrl</kbd> + <kbd>-</kbd> | Zoom out
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>Z</kbd> <br> <kbd>=</kbd> <br> <kbd>Ctrl</kbd> + <kbd>=</kbd> | Reset zoom
 | 
			
		||||
<kbd>F</kbd> <br> <kbd>F11</kbd> <br> <kbd>Alt + Return</kbd> <br> <kbd>Alt + Enter</kbd> | Fullscreen image viewer
 | 
			
		||||
<kbd>H</kbd> <br> <kbd>Left</kbd> <br> <kbd>Alt + H</kbd> <br> <kbd>Alt + Left</kbd> | Pan image left
 | 
			
		||||
<kbd>J</kbd> <br> <kbd>Down</kbd> <br> <kbd>Alt + J</kbd> <br> <kbd>Alt + Down</kbd> | Pan image down
 | 
			
		||||
<kbd>K</kbd> <br> <kbd>Up</kbd> <br> <kbd>Alt + K</kbd> <br> <kbd>Alt + Up</kbd> | Pan image up
 | 
			
		||||
<kbd>L</kbd> <br> <kbd>Right</kbd> <br> <kbd>Alt + L</kbd> <br> <kbd>Alt + Right</kbd> | Pan image right
 | 
			
		||||
<kbd>Z</kbd> <br> <kbd>+</kbd> <br> <kbd>Ctrl + +</kbd> | Zoom in
 | 
			
		||||
<kbd>Shift + Z</kbd> <br> <kbd>-</kbd> <br> <kbd>Ctrl + -</kbd> | Zoom out
 | 
			
		||||
<kbd>Alt + Z</kbd> <br> <kbd>=</kbd> <br> <kbd>Ctrl + =</kbd> | Reset zoom
 | 
			
		||||
<kbd>R</kbd> | Rotate image right
 | 
			
		||||
<kbd>Shift</kbd> + <kbd>R</kbd> | Rotate image left
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>R</kbd> | Reset image rotation
 | 
			
		||||
<kbd>Shift + R</kbd> | Rotate image left
 | 
			
		||||
<kbd>Alt + R</kbd> | Reset image rotation
 | 
			
		||||
<kbd>S</kbd> | Speed up gif
 | 
			
		||||
<kbd>Shift</kbd> + <kbd>S</kbd> | Slow down gif
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>S</kbd> | Reset gif speed
 | 
			
		||||
<kbd>Shift + S</kbd> | Slow down gif
 | 
			
		||||
<kbd>Alt + S</kbd> | Reset gif speed
 | 
			
		||||
<kbd>Space</kbd> | Pause gif
 | 
			
		||||
 | 
			
		||||
## Security tab bindings
 | 
			
		||||
@@ -142,8 +142,10 @@ Key | Function
 | 
			
		||||
Key | Function
 | 
			
		||||
------ | ------
 | 
			
		||||
<kbd>Tab</kbd> | Navigate next
 | 
			
		||||
<kbd>Shift</kbd> + <kbd>Tab</kbd> | Navigate previous
 | 
			
		||||
<kbd>Shift + Tab</kbd> | Navigate previous
 | 
			
		||||
<kbd>Space</kbd> | Toggle check
 | 
			
		||||
<kbd>Menu</kbd> | Session context menu
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>R</kbd> <br> <kbd>F5</kbd> | Refresh session list
 | 
			
		||||
<kbd>Alt</kbd> + <kbd>S</kbd> <br> <kbd>Delete</kbd> | Sign out session
 | 
			
		||||
<kbd>Alt + R</kbd> <br> <kbd>F5</kbd> | Refresh session list
 | 
			
		||||
<kbd>Alt + S</kbd> <br> <kbd>Delete</kbd> | Sign out session
 | 
			
		||||
 | 
			
		||||
*Binding different than in Mirage
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										69
									
								
								docs/MIRAGEDIFF.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,69 @@
 | 
			
		||||
# Differences between Moment and Mirage
 | 
			
		||||
 | 
			
		||||
## Maintenance
 | 
			
		||||
 | 
			
		||||
Due to access issues, it is unlikely
 | 
			
		||||
<a href="https://github.com/mirukana/mirage">Mirage</a>
 | 
			
		||||
will continue being maintained. You should check the
 | 
			
		||||
<a href="https://github.com/mirukana/mirage/commits/master">
 | 
			
		||||
date of the last commit</a> to make sure.
 | 
			
		||||
 | 
			
		||||
## UI
 | 
			
		||||
 | 
			
		||||
Moment adds a new default theme. If you would rather have the default
 | 
			
		||||
Mirage theme instead, add this to `~/.config/moment/settings.py`:
 | 
			
		||||
``` python
 | 
			
		||||
class General:
 | 
			
		||||
    theme: str = "Midnight.qpl"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
There are currently no differences in features,
 | 
			
		||||
although Moment does have some bug fixes that Mirage does not.
 | 
			
		||||
 | 
			
		||||
## Configuration
 | 
			
		||||
 | 
			
		||||
Moment has a different configuration directory
 | 
			
		||||
(you will get an option to migrate your Mirage
 | 
			
		||||
config automatically on first startup).
 | 
			
		||||
 | 
			
		||||
Moment has different default keybindings:
 | 
			
		||||
- <kbd>Ctrl + Q</kbd> is `Quit`
 | 
			
		||||
- `Reply` is <kbd>Ctrl + R</kbd> instead of <kbd>Ctrl + Q</kbd>
 | 
			
		||||
- `Remove` is <kbd>Ctrl + Shift + R</kbd> instead of <kbd>Ctrl + R</kbd>
 | 
			
		||||
- <kbd>Ctrl + K</kbd> is `Focus filter`
 | 
			
		||||
- `Focus previous message` is <kbd>Ctrl + I</kbd> instead of  <kbd>Ctrl + K</kbd>
 | 
			
		||||
 | 
			
		||||
If you wish to have `Focus previous message` and `Focus next message`
 | 
			
		||||
adjacent on <kbd>Ctrl + U</kbd> and <kbd>Ctrl + I</kbd>,
 | 
			
		||||
we recommend swapping <kbd>Ctrl + U</kbd> and <kbd>Ctrl + J</kbd>
 | 
			
		||||
by adding this to `~/.config/moment/settings.py`:
 | 
			
		||||
``` python
 | 
			
		||||
class Keys:
 | 
			
		||||
 | 
			
		||||
    class Rooms:
 | 
			
		||||
        latest_unread = ["Ctrl+J"]
 | 
			
		||||
 | 
			
		||||
    class Messages:
 | 
			
		||||
        next = ["Ctrl+Down", "Ctrl+U"]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Reverting to default Mirage bindings
 | 
			
		||||
 | 
			
		||||
If you wish to revert to default Mirage bindings,
 | 
			
		||||
add this to `~/.config/moment/settings.py`:
 | 
			
		||||
``` python
 | 
			
		||||
class Keys:
 | 
			
		||||
 | 
			
		||||
    quit = []
 | 
			
		||||
 | 
			
		||||
    class Rooms:
 | 
			
		||||
        focus_filter = ["Alt+F"]
 | 
			
		||||
        latest_unread = ["Ctrl+J"]
 | 
			
		||||
 | 
			
		||||
    class Messages:
 | 
			
		||||
        reply = ["Ctrl+Q"]
 | 
			
		||||
        remove = ["Ctrl+R"]
 | 
			
		||||
        previous = ["Ctrl+Up", "Ctrl+K"]
 | 
			
		||||
```
 | 
			
		||||
@@ -11,7 +11,7 @@ for example:
 | 
			
		||||
Or for Flatpak users:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
    cp mirage/src/themes/Midnight.qpl \
 | 
			
		||||
    cp mirage/src/themes/Foliage.qpl \
 | 
			
		||||
       ~/.var/app/io.github.mirukana.mirage/data/mirage/themes/MyTheme.qpl
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								docs/screenshots/m01-chat.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 359 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/screenshots/m02-sign-in.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 390 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/screenshots/m03-account-settings.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 388 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/screenshots/m04-create-room.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 374 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/screenshots/m05-main-pane-small.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 84 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/screenshots/m06-chat-small.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 94 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/screenshots/m07-room-pane-small.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 80 KiB  | 
@@ -27,7 +27,7 @@ QRC_FILE = $$BUILD_DIR/resources.qrc
 | 
			
		||||
RESOURCES += $$QRC_FILE
 | 
			
		||||
HEADERS   += $$glob_filenames(*.h) submodules/hsluv-c/src/hsluv.h
 | 
			
		||||
SOURCES   += $$glob_filenames(*.cpp) submodules/hsluv-c/src/hsluv.c
 | 
			
		||||
TARGET     = mirage
 | 
			
		||||
TARGET     = moment
 | 
			
		||||
 | 
			
		||||
unix:!macx {
 | 
			
		||||
    LIBS += -lX11 -lXss
 | 
			
		||||
@@ -61,12 +61,12 @@ no-x11 {
 | 
			
		||||
    executables.files = $$TARGET
 | 
			
		||||
 | 
			
		||||
    shortcuts.path  = $$PREFIX/share/applications
 | 
			
		||||
    shortcuts.files = packaging/mirage.desktop
 | 
			
		||||
    shortcuts.files = packaging/moment.desktop
 | 
			
		||||
 | 
			
		||||
    icons256.path  = $$PREFIX/share/icons/hicolor/256x256/apps
 | 
			
		||||
    icons256.files = packaging/mirage.png
 | 
			
		||||
    icons256.files = packaging/moment.png
 | 
			
		||||
 | 
			
		||||
    examples.path  = $$PREFIX/share/examples/mirage
 | 
			
		||||
    examples.path  = $$PREFIX/share/examples/moment
 | 
			
		||||
    examples.files = src/config/settings.py
 | 
			
		||||
 | 
			
		||||
    INSTALLS += executables shortcuts icons256 examples
 | 
			
		||||
@@ -101,7 +101,7 @@ for(file, $$list($$glob_filenames(*.py))) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QMAKE_CLEAN *= $$MOC_DIR $$OBJECTS_DIR $$RCC_DIR $$PYCACHE_DIRS $$QRC_FILE
 | 
			
		||||
QMAKE_CLEAN *= $$BUILD_DIR $$TARGET Makefile mirage.pro.user .qmake.stash
 | 
			
		||||
QMAKE_CLEAN *= $$BUILD_DIR $$TARGET Makefile moment.pro.user .qmake.stash
 | 
			
		||||
QMAKE_CLEAN *= $$glob_filenames(*.pyc, *.qmlc, *.jsc, *.egg-info)
 | 
			
		||||
QMAKE_CLEAN *= packaging/flatpak/flatpak-env
 | 
			
		||||
QMAKE_CLEAN *= packaging/flatpak/flatpak-pip-generator
 | 
			
		||||
@@ -1,16 +0,0 @@
 | 
			
		||||
#!/usr/bin/env sh
 | 
			
		||||
set -e
 | 
			
		||||
 | 
			
		||||
here="$(dirname "$(readlink -f "$0")")"
 | 
			
		||||
 | 
			
		||||
export RESTORE_LD_LIBRARY_PATH="$LD_LIBRARY_PATH"
 | 
			
		||||
export RESTORE_PYTHONHOME="$PYTHONHOME"
 | 
			
		||||
export RESTORE_PYTHONUSERBASE="$PYTHONUSERBASE"
 | 
			
		||||
 | 
			
		||||
export SSL_CERT_FILE="$here/usr/lib/python$PY_XY/site-packages/certifi/cacert.pem"
 | 
			
		||||
export LD_LIBRARY_PATH="$here/usr/lib:$LD_LIBRARY_PATH"
 | 
			
		||||
export PYTHONHOME="$here/usr"
 | 
			
		||||
export PYTHONUSERBASE="$here/usr"
 | 
			
		||||
 | 
			
		||||
cd "$here"
 | 
			
		||||
exec "$here/usr/bin/mirage" "$@"
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
# AppImage building
 | 
			
		||||
 | 
			
		||||
The image must be built on Ubuntu 16.04 Xenial, to ensure compatibility with
 | 
			
		||||
older systems.
 | 
			
		||||
 | 
			
		||||
LXD can be used to setup a suitable container from any distro.
 | 
			
		||||
 | 
			
		||||
If not done already (all default settings are usually fine):
 | 
			
		||||
 | 
			
		||||
    lxd init
 | 
			
		||||
 | 
			
		||||
Initialize a new container named `ubuntu`:
 | 
			
		||||
 | 
			
		||||
    lxc launch images:ubuntu/xenial/amd64 ubuntu
 | 
			
		||||
 | 
			
		||||
Now, you can either clone the repo from inside the container...:
 | 
			
		||||
 | 
			
		||||
    lxc exec ubuntu -- apt install -y git
 | 
			
		||||
    lxc exec ubuntu -- git pull https://github.com/mirukana/mirage
 | 
			
		||||
 | 
			
		||||
...or directly copy a repository from your local filesystem inside:
 | 
			
		||||
 | 
			
		||||
    lxc exec ubuntu -- /bin/mkdir -p /root/mirage
 | 
			
		||||
    lxc file push -vr <path to repo root>/* ubuntu/root/mirage
 | 
			
		||||
 | 
			
		||||
Run the build script inside the container:
 | 
			
		||||
 | 
			
		||||
    lxc exec ubuntu -- /root/mirage/packaging/appimage/build.sh
 | 
			
		||||
 | 
			
		||||
You can also start a shell inside (e.g. if something goes wrong):
 | 
			
		||||
 | 
			
		||||
    lxc exec ubuntu -- /bin/bash
 | 
			
		||||
@@ -1,252 +0,0 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -eo pipefail
 | 
			
		||||
 | 
			
		||||
HERE="$(dirname "$(readlink -f "$0")")"
 | 
			
		||||
MIRAGE_REPO_URL='https://github.com/mirukana/mirage'
 | 
			
		||||
PY_XYZ=3.9.6
 | 
			
		||||
PY_XY="$(cut -d . -f 1-2 <<< "$PY_XYZ")"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
check_distro() {
 | 
			
		||||
    if grep -q '^\s*Ubuntu\s*16.04' /etc/issue; then return; fi
 | 
			
		||||
 | 
			
		||||
    echo "Not running on expected distribution or version, aborting!" >&2
 | 
			
		||||
    echo "See <repo root>/packaging/appimage/README.md for more info." >&2
 | 
			
		||||
    exit 99
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
parse_cli_arguments() {
 | 
			
		||||
    if [ "$1" = --skip-install-prerequisites ] || [ "$1" = -s ]; then
 | 
			
		||||
        skip_pre=true
 | 
			
		||||
    else
 | 
			
		||||
        skip_pre=false
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
setup_dns() {
 | 
			
		||||
    if ! grep -q 'dns-nameservers 9.9.9.9' /etc/network/interfaces; then
 | 
			
		||||
        sed -i '/iface eth0 inet dhcp/a dns-nameservers 9.9.9.9' \
 | 
			
		||||
            /etc/network/interfaces
 | 
			
		||||
 | 
			
		||||
        invoke-rc.d networking restart
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
install_apt_packages() {
 | 
			
		||||
    apt install -y software-properties-common
 | 
			
		||||
    add-apt-repository -y ppa:beineri/opt-qt-5.12.9-xenial
 | 
			
		||||
    add-apt-repository -y ppa:beineri/opt-qt-5.12.9-xenial
 | 
			
		||||
    apt update -y
 | 
			
		||||
 | 
			
		||||
    apt install -y \
 | 
			
		||||
        qt512base qt512declarative qt512graphicaleffects \
 | 
			
		||||
        qt512imageformats qt512quickcontrols2 qt512svg \
 | 
			
		||||
        zip git wget cmake ccache \
 | 
			
		||||
        build-essential mesa-common-dev libglu1-mesa-dev freeglut3-dev \
 | 
			
		||||
        libglfw3-dev libgles2-mesa-dev libssl-dev \
 | 
			
		||||
        python3-dev python3-setuptools python3-pip libgdbm-dev libc6-dev \
 | 
			
		||||
        libsqlite3-dev libffi-dev openssl libreadline-dev \
 | 
			
		||||
        libjpeg-turbo8-dev zlib1g-dev \
 | 
			
		||||
        libtiff5-dev liblcms2-dev libwebp-dev  libopenjp2-7-dev \
 | 
			
		||||
        libx11-dev libxss-dev libasound2-dev \
 | 
			
		||||
        pkg-config libdbus-1-dev libglib2.0-dev \
 | 
			
		||||
        appstream-util desktop-file-utils  # for appimage-lint.sh
 | 
			
		||||
 | 
			
		||||
    /usr/sbin/update-ccache-symlinks
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
setup_env() {
 | 
			
		||||
    set +euo pipefail
 | 
			
		||||
    # shellcheck disable=SC1091
 | 
			
		||||
    source /opt/qt512/bin/qt512-env.sh
 | 
			
		||||
    set -euo pipefail
 | 
			
		||||
 | 
			
		||||
    export PATH="/usr/lib/ccache:$PATH"
 | 
			
		||||
    export LD_LIBRARY_PATH="$HOME/.local/lib/python$PY_XY/site-packages/PIL/.libs/:$HOME/.local/lib/python$PY_XY/site-packages/.libs_cffi_backend/:/usr/lib/x86_64-linux-gnu/:/usr/lib:$LD_LIBRARY_PATH"
 | 
			
		||||
    export PREFIX=/usr
 | 
			
		||||
 | 
			
		||||
    export CFLAGS="-march=x86-64 -O2 -pipe -fPIC"
 | 
			
		||||
    export CXXFLAGS="$CFLAGS"
 | 
			
		||||
    export MAKEFLAGS="-j$(($(nproc) + 1))"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
install_python() {
 | 
			
		||||
    cd ~
 | 
			
		||||
 | 
			
		||||
    if ! [ -d ~/.pyenv ]; then
 | 
			
		||||
        wget -O - https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    export PYENV_ROOT="$HOME/.pyenv"
 | 
			
		||||
    export PATH="$PYENV_ROOT/bin:$PATH"
 | 
			
		||||
 | 
			
		||||
    set +euo pipefail
 | 
			
		||||
    eval "$(pyenv init --path)"
 | 
			
		||||
    set -euo pipefail
 | 
			
		||||
 | 
			
		||||
    export PYTHON_CFLAGS="$CFLAGS"
 | 
			
		||||
    export PYTHON_CONFIGURE_OPTS='--enable-shared  --enable-optimizations --with-lto'
 | 
			
		||||
 | 
			
		||||
    pyenv update
 | 
			
		||||
    pyenv install --verbose --skip-existing $PY_XYZ
 | 
			
		||||
    pyenv global $PY_XYZ
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
install_olm() {
 | 
			
		||||
    cd ~
 | 
			
		||||
 | 
			
		||||
    rm -rf olm-master.tar.gz olm-master
 | 
			
		||||
    wget 'https://gitlab.matrix.org/matrix-org/olm/-/archive/master/olm-master.tar.gz'
 | 
			
		||||
    tar xf olm-master.tar.gz
 | 
			
		||||
 | 
			
		||||
    cd olm-master
 | 
			
		||||
    make clean
 | 
			
		||||
    cmake . -Bbuild
 | 
			
		||||
    cmake --build build
 | 
			
		||||
    make install
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
install_pyotherside() {
 | 
			
		||||
    cd ~
 | 
			
		||||
 | 
			
		||||
    if ! [ -f 1.5.9.tar.gz ]; then
 | 
			
		||||
        wget 'https://github.com/thp/pyotherside/archive/1.5.9.tar.gz'
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    tar xf 1.5.9.tar.gz
 | 
			
		||||
 | 
			
		||||
    cd pyotherside-1.5.9
 | 
			
		||||
    make clean
 | 
			
		||||
    find . -name Makefile -delete
 | 
			
		||||
    qmake
 | 
			
		||||
    make install
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
get_app_and_pip_dependencies() {
 | 
			
		||||
    cd ~
 | 
			
		||||
 | 
			
		||||
    if ! [ -d mirage ]; then
 | 
			
		||||
        git clone --recursive "$MIRAGE_REPO_URL"
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    cd mirage
 | 
			
		||||
    if pip3 show Pillow; then pip3 uninstall Pillow --yes; fi
 | 
			
		||||
    pip3 install Pillow --no-binary :all:
 | 
			
		||||
    pip3 install --user -Ur requirements.txt
 | 
			
		||||
    pip3 install --user -U certifi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
initialize_appdir() {
 | 
			
		||||
    cd ~/mirage
 | 
			
		||||
    rm -rf .qmake.stash Makefile build
 | 
			
		||||
 | 
			
		||||
    qmake mirage.pro PREFIX=/usr
 | 
			
		||||
    make install INSTALL_ROOT=build/appdir
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
complete_appdir() {
 | 
			
		||||
    cd ~/mirage/build
 | 
			
		||||
 | 
			
		||||
    cp -r ~/.pyenv/versions/$PY_XYZ/* appdir/usr
 | 
			
		||||
    cp -r "$HOME/.local/lib/python$PY_XY/site-packages/"* \
 | 
			
		||||
          "appdir/usr/lib/python$PY_XY/site-packages"
 | 
			
		||||
 | 
			
		||||
    cd ~/mirage/build/appdir/usr/lib
 | 
			
		||||
    ln -s "python$PY_XY/site-packages/Pillow.libs/"* .
 | 
			
		||||
    cd ~/mirage/build
 | 
			
		||||
 | 
			
		||||
    if ! [ -f ~/linuxdeployqt.AppImage ]; then
 | 
			
		||||
        wget 'https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage' \
 | 
			
		||||
             -O ~/linuxdeployqt.AppImage
 | 
			
		||||
    fi
 | 
			
		||||
    chmod +x ~/linuxdeployqt.AppImage
 | 
			
		||||
 | 
			
		||||
    ~/linuxdeployqt.AppImage appdir/usr/share/applications/mirage.desktop \
 | 
			
		||||
                             -bundle-non-qt-libs -qmldir=../src/gui
 | 
			
		||||
 | 
			
		||||
    mkdir -p appdir/usr/share/metainfo
 | 
			
		||||
    cp ~/mirage/packaging/mirage.appdata.xml appdir/usr/share/metainfo
 | 
			
		||||
 | 
			
		||||
    cp /opt/qt512/qml/io/thp/pyotherside/qmldir \
 | 
			
		||||
       appdir/usr/qml/io/thp/pyotherside
 | 
			
		||||
 | 
			
		||||
    # Remove useless heavy test data
 | 
			
		||||
    rm -rf "appdir/usr/lib/python$PY_XY/test"
 | 
			
		||||
    rm -rf "appdir/usr/lib/python$PY_XY/site-packages/Crypto/SelfTest/"
 | 
			
		||||
 | 
			
		||||
    # Remove python cache files
 | 
			
		||||
    find appdir -name '*.pyc' -delete
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
fix_apprun_launcher() {
 | 
			
		||||
    cd ~/mirage/build/appdir
 | 
			
		||||
    rm -f AppRun  # because it's a symlink
 | 
			
		||||
    sed "s/\\\$PY_XY/$PY_XY/" "$HERE/AppRun.sh" > AppRun
 | 
			
		||||
    chmod +x AppRun
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
generate_appimage() {
 | 
			
		||||
    cd ~/mirage/build
 | 
			
		||||
 | 
			
		||||
    if ! [ -f ~/appimagetool.AppImage ]; then
 | 
			
		||||
        wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" \
 | 
			
		||||
             -O ~/appimagetool.AppImage
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    chmod +x ~/appimagetool.AppImage
 | 
			
		||||
    ~/appimagetool.AppImage --no-appstream appdir
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
lint_appdir() {
 | 
			
		||||
    cd ~
 | 
			
		||||
 | 
			
		||||
    cat << 'EOF' > /usr/local/bin/mimetype
 | 
			
		||||
#!/usr/bin/env sh
 | 
			
		||||
file --mime-type "$@" | tr -d ';'
 | 
			
		||||
EOF
 | 
			
		||||
    chmod +x /usr/local/bin/mimetype
 | 
			
		||||
 | 
			
		||||
    if ! [ -d pkg2appimage ]; then
 | 
			
		||||
        git clone https://github.com/AppImage/pkg2appimage
 | 
			
		||||
    fi
 | 
			
		||||
    chmod +x pkg2appimage/appdir-lint.sh
 | 
			
		||||
 | 
			
		||||
    cd ~/mirage/build
 | 
			
		||||
    echo -e "\e[34m\nAppDir linting result:\n\e[0m"
 | 
			
		||||
    ~/pkg2appimage/appdir-lint.sh appdir
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
check_distro
 | 
			
		||||
parse_cli_arguments "$@"
 | 
			
		||||
setup_dns
 | 
			
		||||
 | 
			
		||||
if [ "$skip_pre" = false ]; then install_apt_packages; fi
 | 
			
		||||
 | 
			
		||||
setup_env
 | 
			
		||||
 | 
			
		||||
if [ "$skip_pre" = false ]; then
 | 
			
		||||
    install_python
 | 
			
		||||
    install_olm
 | 
			
		||||
    install_pyotherside
 | 
			
		||||
    get_app_and_pip_dependencies
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
initialize_appdir
 | 
			
		||||
complete_appdir
 | 
			
		||||
fix_apprun_launcher
 | 
			
		||||
generate_appimage
 | 
			
		||||
lint_appdir
 | 
			
		||||
@@ -19,4 +19,4 @@ async_generator   >= 1.10,  < 2;   python_version < "3.7"
 | 
			
		||||
dataclasses       >= 0.6,   < 0.7; python_version < "3.7"
 | 
			
		||||
pyfastcopy        >= 1.0.3, < 2;   python_version < "3.8"
 | 
			
		||||
 | 
			
		||||
git+https://github.com/mirukana/matrix-nio#egg-matrix-nio[e2e]
 | 
			
		||||
git+https://github.com/MRAAGH/matrix-nio#egg-matrix-nio[e2e]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,8 @@
 | 
			
		||||
# Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
 | 
			
		||||
# and Moment contributors <https://gitlab.com/mx-moment/moment>
 | 
			
		||||
# SPDX-License-Identifier: LGPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
"""This package provides Mirage's backend side that can interact with the UI.
 | 
			
		||||
"""This package provides Moment's backend side that can interact with the UI.
 | 
			
		||||
 | 
			
		||||
To learn more about how this package works, you might want to check the
 | 
			
		||||
documentation in the following modules first:
 | 
			
		||||
@@ -12,7 +13,7 @@ documentation in the following modules first:
 | 
			
		||||
- `nio_callbacks`
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
__app_name__     = "mirage"
 | 
			
		||||
__display_name__ = "Mirage"
 | 
			
		||||
__reverse_dns__  = "io.github.mirukana.mirage"
 | 
			
		||||
__version__      = "0.7.2"
 | 
			
		||||
__app_name__     = "moment"
 | 
			
		||||
__display_name__ = "Moment"
 | 
			
		||||
__reverse_dns__  = "xyz.mx-moment"
 | 
			
		||||
__version__      = "0.7.3"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
# Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
 | 
			
		||||
# and Moment contributors <https://gitlab.com/mx-moment/moment>
 | 
			
		||||
# SPDX-License-Identifier: LGPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
import asyncio
 | 
			
		||||
@@ -104,7 +105,7 @@ class Backend:
 | 
			
		||||
        media_cache: A matrix media cache for downloaded files.
 | 
			
		||||
 | 
			
		||||
        presences: A `{user_id: Presence}` dict for storing presence info about
 | 
			
		||||
            matrix users registered on Mirage.
 | 
			
		||||
            matrix users registered on Moment.
 | 
			
		||||
 | 
			
		||||
        mxc_events: A dict storing media `Event` model items for any account
 | 
			
		||||
            that have the same mxc URI
 | 
			
		||||
@@ -137,7 +138,7 @@ class Backend:
 | 
			
		||||
            DefaultDict(asyncio.Lock)  # {room_id: lock}
 | 
			
		||||
 | 
			
		||||
        cache_dir = Path(
 | 
			
		||||
            os.environ.get("MIRAGE_CACHE_DIR") or self.appdirs.user_cache_dir,
 | 
			
		||||
            os.environ.get("MOMENT_CACHE_DIR") or self.appdirs.user_cache_dir,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.media_cache: MediaCache = MediaCache(self, cache_dir)
 | 
			
		||||
@@ -552,7 +553,13 @@ class Backend:
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        api_list = "https://publiclist.anchel.nl/publiclist.json"
 | 
			
		||||
        response = await session.get(api_list)
 | 
			
		||||
        try:
 | 
			
		||||
            response = await session.get(api_list)
 | 
			
		||||
        except:
 | 
			
		||||
            await session.close()
 | 
			
		||||
            print("Unable to fetch", api_list)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        coros    = []
 | 
			
		||||
 | 
			
		||||
        for server in (await response.json()):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
# Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
 | 
			
		||||
# and Moment contributors <https://gitlab.com/mx-moment/moment>
 | 
			
		||||
# SPDX-License-Identifier: LGPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
"""Matrix media downloading, caching and retrieval."""
 | 
			
		||||
@@ -95,7 +96,7 @@ class Media:
 | 
			
		||||
            <base download folder>/<homeserver domain>/
 | 
			
		||||
            <file title>_<mxc id>.<file extension>`
 | 
			
		||||
        ```
 | 
			
		||||
        e.g. `~/.cache/mirage/downloads/matrix.org/foo_Hm24ar11i768b0el.png`.
 | 
			
		||||
        e.g. `~/.cache/moment/downloads/matrix.org/foo_Hm24ar11i768b0el.png`.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        parsed   = urlparse(self.mxc)
 | 
			
		||||
@@ -304,7 +305,7 @@ class Thumbnail(Media):
 | 
			
		||||
            <file title>_<mxc id>.<file extension>`
 | 
			
		||||
        ```
 | 
			
		||||
        e.g.
 | 
			
		||||
        `~/.cache/mirage/thumbnails/matrix.org/32x32/foo_Hm24ar11i768b0el.png`.
 | 
			
		||||
        `~/.cache/moment/thumbnails/matrix.org/32x32/foo_Hm24ar11i768b0el.png`.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        size     = self.normalize_size(self.server_size or self.wanted_size)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
# Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
 | 
			
		||||
# and Moment contributors <https://gitlab.com/mx-moment/moment>
 | 
			
		||||
# SPDX-License-Identifier: LGPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
"""User data and configuration files definitions."""
 | 
			
		||||
@@ -197,7 +198,7 @@ class ConfigFile(UserFile):
 | 
			
		||||
    @property
 | 
			
		||||
    def path(self) -> Path:
 | 
			
		||||
        return Path(
 | 
			
		||||
            os.environ.get("MIRAGE_CONFIG_DIR") or
 | 
			
		||||
            os.environ.get("MOMENT_CONFIG_DIR") or
 | 
			
		||||
            self.backend.appdirs.user_config_dir,
 | 
			
		||||
        ) / self.filename
 | 
			
		||||
 | 
			
		||||
@@ -209,7 +210,7 @@ class UserDataFile(UserFile):
 | 
			
		||||
    @property
 | 
			
		||||
    def path(self) -> Path:
 | 
			
		||||
        return Path(
 | 
			
		||||
            os.environ.get("MIRAGE_DATA_DIR") or
 | 
			
		||||
            os.environ.get("MOMENT_DATA_DIR") or
 | 
			
		||||
            self.backend.appdirs.user_data_dir,
 | 
			
		||||
        ) / self.filename
 | 
			
		||||
 | 
			
		||||
@@ -460,7 +461,7 @@ class NewTheme(UserDataFile, PCNFile):
 | 
			
		||||
    @property
 | 
			
		||||
    def path(self) -> Path:
 | 
			
		||||
        data_dir = Path(
 | 
			
		||||
            os.environ.get("MIRAGE_DATA_DIR") or
 | 
			
		||||
            os.environ.get("MOMENT_DATA_DIR") or
 | 
			
		||||
            self.backend.appdirs.user_data_dir,
 | 
			
		||||
        )
 | 
			
		||||
        return data_dir / "themes" / self.filename
 | 
			
		||||
@@ -515,17 +516,17 @@ class Theme(UserDataFile):
 | 
			
		||||
    @property
 | 
			
		||||
    def path(self) -> Path:
 | 
			
		||||
        data_dir = Path(
 | 
			
		||||
            os.environ.get("MIRAGE_DATA_DIR") or
 | 
			
		||||
            os.environ.get("MOMENT_DATA_DIR") or
 | 
			
		||||
            self.backend.appdirs.user_data_dir,
 | 
			
		||||
        )
 | 
			
		||||
        return data_dir / "themes" / self.filename
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def default_data(self) -> str:
 | 
			
		||||
        if self.filename in ("Midnight.qpl", "Glass.qpl"):
 | 
			
		||||
        if self.filename in ("Foliage.qpl", "Midnight.qpl", "Glass.qpl"):
 | 
			
		||||
            path = f"src/themes/{self.filename}"
 | 
			
		||||
        else:
 | 
			
		||||
            path = "src/themes/Midnight.qpl"
 | 
			
		||||
            path = "src/themes/Foliage.qpl"
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            byte_content = pyotherside.qrc_get_file_contents(path)
 | 
			
		||||
 
 | 
			
		||||
@@ -26,13 +26,14 @@ class General:
 | 
			
		||||
    tooltips_delay: float = 0.7
 | 
			
		||||
 | 
			
		||||
    # Application theme to use.
 | 
			
		||||
    # Can be the name of a built-in theme (Mirage.qpl or Glass.qpl), or
 | 
			
		||||
    # the name (including extension) of a file in the user theme folder, which
 | 
			
		||||
    # is "$XDG_DATA_HOME/mirage/themes" if that environment variable is set,
 | 
			
		||||
    # else "~/.local/share/mirage/themes".
 | 
			
		||||
    # Can be the name of a built-in theme (Foliage.qpl, Midnight.qpl or
 | 
			
		||||
    # Glass.qpl), or the name (including extension) of a file in the user theme
 | 
			
		||||
    # folder, which is "$XDG_DATA_HOME/moment/themes" if that environment
 | 
			
		||||
    # variable is set,
 | 
			
		||||
    # else "~/.local/share/moment/themes".
 | 
			
		||||
    # For Flatpak, it is
 | 
			
		||||
    # "~/.var/app/io.github.mirukana.mirage/data/mirage/themes".
 | 
			
		||||
    theme: str = "Midnight.qpl"
 | 
			
		||||
    theme: str = "Foliage.qpl"
 | 
			
		||||
 | 
			
		||||
    # Interface scale multiplier, e.g. 0.5 makes everything half-size.
 | 
			
		||||
    zoom: float = 1.0
 | 
			
		||||
@@ -204,7 +205,7 @@ class Chat:
 | 
			
		||||
        auto_play_gif: bool = True
 | 
			
		||||
 | 
			
		||||
        # When clicking on a file in the timeline, open it in an external
 | 
			
		||||
        # program instead of displaying it using Mirage's interface.
 | 
			
		||||
        # program instead of displaying it using Moment's interface.
 | 
			
		||||
        # On Linux, the xdg-open command is called.
 | 
			
		||||
        click_opens_externally: bool = False
 | 
			
		||||
 | 
			
		||||
@@ -234,7 +235,7 @@ class Keys:
 | 
			
		||||
    # would need to be pressed.
 | 
			
		||||
    #
 | 
			
		||||
    # A list of default bindings can be found at:
 | 
			
		||||
    # https://github.com/mirukana/mirage/blob/master/docs/KEYBINDINGS.md
 | 
			
		||||
    # https://gitlab.com/mx-moment/moment/-/blob/main/docs/KEYBINDINGS.md
 | 
			
		||||
 | 
			
		||||
    # Helper functions
 | 
			
		||||
 | 
			
		||||
@@ -280,7 +281,7 @@ class Keys:
 | 
			
		||||
    qml_console = ["F1"]
 | 
			
		||||
 | 
			
		||||
    # Start the Python backend debugger.
 | 
			
		||||
    # Mirage must be connected to a terminal for this to work.
 | 
			
		||||
    # Moment must be connected to a terminal for this to work.
 | 
			
		||||
    python_debugger = ["Shift+F1"]
 | 
			
		||||
 | 
			
		||||
    # Start the Python backend debugger in remote access mode.
 | 
			
		||||
@@ -288,8 +289,8 @@ class Keys:
 | 
			
		||||
    # From any terminal, run `socat readline tcp:127.0.0.1:4444` to connect.
 | 
			
		||||
    python_remote_debugger = ["Alt+F1"]
 | 
			
		||||
 | 
			
		||||
    # Quit Mirage
 | 
			
		||||
    quit = []
 | 
			
		||||
    # Quit Moment
 | 
			
		||||
    quit = ["Ctrl+Q"]
 | 
			
		||||
 | 
			
		||||
    class Scrolling: # Keys.Scrolling
 | 
			
		||||
        # Pages and chat timeline scrolling
 | 
			
		||||
@@ -349,7 +350,7 @@ class Keys:
 | 
			
		||||
        # When focusing the field, use Tab/Shift+Tab or the arrows to navigate
 | 
			
		||||
        # the list, Enter to switch to focused account/room, Escape to cancel,
 | 
			
		||||
        # Menu to open the context menu.
 | 
			
		||||
        focus_filter = ["Alt+F"]
 | 
			
		||||
        focus_filter = ["Alt+F","Ctrl+K"]
 | 
			
		||||
        clear_filter = ["Alt+Shift+F"]
 | 
			
		||||
 | 
			
		||||
        # Switch to the previous/next room in the list.
 | 
			
		||||
@@ -431,7 +432,7 @@ class Keys:
 | 
			
		||||
        # Focus the previous/next message in the timeline.
 | 
			
		||||
        # Keybinds defined below in this section affect the focused message.
 | 
			
		||||
        # The Menu key can open the context menu for a focused message.
 | 
			
		||||
        previous = ["Ctrl+Up", "Ctrl+K"]
 | 
			
		||||
        previous = ["Ctrl+Up", "Ctrl+I"]
 | 
			
		||||
        next     = ["Ctrl+Down", "Ctrl+J"]
 | 
			
		||||
 | 
			
		||||
        # Select the currently focused message, same as clicking on it.
 | 
			
		||||
@@ -461,12 +462,12 @@ class Keys:
 | 
			
		||||
 | 
			
		||||
        # Remove the selected messages if any, else the focused message if any,
 | 
			
		||||
        # else the last message you posted.
 | 
			
		||||
        remove = ["Ctrl+R", "Alt+Del"]
 | 
			
		||||
        remove = ["Ctrl+Shift+R", "Alt+Del"]
 | 
			
		||||
 | 
			
		||||
        # Reply/cancel reply to the focused message if any,
 | 
			
		||||
        # else the last message posted by someone else.
 | 
			
		||||
        # Replying can also be cancelled by pressing Escape.
 | 
			
		||||
        reply = ["Ctrl+Q"]
 | 
			
		||||
        reply = ["Ctrl+R"]
 | 
			
		||||
 | 
			
		||||
        # Open the QML developer console for the focused message if any,
 | 
			
		||||
        # and display the event source.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
// Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
 | 
			
		||||
// and Moment contributors <https://gitlab.com/mx-moment/moment>
 | 
			
		||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
pragma Singleton
 | 
			
		||||
@@ -18,9 +19,9 @@ QtObject {
 | 
			
		||||
        -h, --help           Show this help and exit
 | 
			
		||||
 | 
			
		||||
    Environment variables:
 | 
			
		||||
        MIRAGE_CONFIG_DIR  Override the default configuration folder
 | 
			
		||||
        MIRAGE_DATA_DIR    Override the default application data folder
 | 
			
		||||
        MIRAGE_CACHE_DIR   Override the default cache and downloads folder
 | 
			
		||||
        MOMENT_CONFIG_DIR  Override the default configuration folder
 | 
			
		||||
        MOMENT_DATA_DIR    Override the default application data folder
 | 
			
		||||
        MOMENT_CACHE_DIR   Override the default cache and downloads folder
 | 
			
		||||
        http_proxy         Override the General.proxy setting, see settings.py
 | 
			
		||||
    `
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ Rectangle {
 | 
			
		||||
                    icon.name: "documentation"
 | 
			
		||||
                    text: qsTr("Online documentation")
 | 
			
		||||
                    onTriggered: Qt.openUrlExternally(
 | 
			
		||||
                        "https://github.com/mirukana/mirage/tree/master/docs"
 | 
			
		||||
                        "https://gitlab.com/mx-moment/moment/-/tree/main/docs"
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -129,7 +129,7 @@ HListView {
 | 
			
		||||
 | 
			
		||||
    Timer {
 | 
			
		||||
        id: autoSaveTimer
 | 
			
		||||
        interval: 30000
 | 
			
		||||
        interval: 3000
 | 
			
		||||
        onTriggered: root.save()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -246,7 +246,7 @@ HBox {
 | 
			
		||||
    Timer {
 | 
			
		||||
        interval: 1000
 | 
			
		||||
        running:
 | 
			
		||||
            fetchServersFutureId === "" &&
 | 
			
		||||
            // fetchServersFutureId === "" &&
 | 
			
		||||
            ModelStore.get("homeservers").count === 0
 | 
			
		||||
 | 
			
		||||
        repeat: true
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ HFlickableColumnPopup {
 | 
			
		||||
    property string path
 | 
			
		||||
 | 
			
		||||
    readonly property string docs:
 | 
			
		||||
        "https://github.com/mirukana/mirage/tree/master/docs"
 | 
			
		||||
        "https://gitlab.com/mx-moment/moment/-/tree/main/docs"
 | 
			
		||||
 | 
			
		||||
    page.footer: AutoDirectionLayout {
 | 
			
		||||
        CancelButton {
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ HColumnPopup {
 | 
			
		||||
            text: qsTr("Report")
 | 
			
		||||
            icon.name: "report-error"
 | 
			
		||||
            onClicked: Qt.openUrlExternally(
 | 
			
		||||
                "https://github.com/mirukana/mirage/blob/master/docs/" +
 | 
			
		||||
                "https://gitlab.com/mx-moment/moment/-/blob/main/docs/" +
 | 
			
		||||
                "CONTRIBUTING.md#issues"
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/images/foliage.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 246 KiB  | 
							
								
								
									
										250
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						@@ -1,4 +1,5 @@
 | 
			
		||||
// Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
 | 
			
		||||
// and Moment contributors <https://gitlab.com/mx-moment/moment>
 | 
			
		||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
// This file creates the application, registers custom objects for QML
 | 
			
		||||
@@ -17,6 +18,7 @@
 | 
			
		||||
#include <QDir>
 | 
			
		||||
#include <QFile>
 | 
			
		||||
#include <QLockFile>
 | 
			
		||||
#include <QMessageBox>
 | 
			
		||||
#include <signal.h>
 | 
			
		||||
 | 
			
		||||
#ifdef Q_OS_UNIX
 | 
			
		||||
@@ -97,6 +99,236 @@ void onExitSignal(int signum) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void migrateFile(QDir source, QDir destination, QString fname) {
 | 
			
		||||
    if (! QFile::copy(source.filePath(fname), destination.filePath(fname)))
 | 
			
		||||
        qWarning() << "Could not migrate" << fname;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void migrateShallowDirectory(
 | 
			
		||||
        QDir sourceParent, QDir destinationParent, QString dname
 | 
			
		||||
        ) {
 | 
			
		||||
    if (sourceParent.exists(dname)) {
 | 
			
		||||
        if (! destinationParent.mkpath(dname)) {
 | 
			
		||||
            qWarning() << "Could not create directory"
 | 
			
		||||
                << destinationParent.filePath(dname);
 | 
			
		||||
            exit(EXIT_FAILURE);
 | 
			
		||||
        }
 | 
			
		||||
        QDir source(sourceParent.filePath(dname));
 | 
			
		||||
        QDir destination(destinationParent.filePath(dname));
 | 
			
		||||
        QFileInfoList files = source.entryInfoList();
 | 
			
		||||
        for (QFileInfo file : files) {
 | 
			
		||||
            if(file.fileName() == "." || file.fileName() == "..")
 | 
			
		||||
                continue;
 | 
			
		||||
            migrateFile(source, destination, file.fileName());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void offerMigrateFromMirage(
 | 
			
		||||
        QDir configDirMoment, QDir configDirMirage,
 | 
			
		||||
        QDir dataDirMoment, QDir dataDirMirage
 | 
			
		||||
        ) {
 | 
			
		||||
    QMessageBox dialog;
 | 
			
		||||
    dialog.setText("Would you like Moment to re-use your logins, "
 | 
			
		||||
            "encryption keys, configuration and themes from Mirage?");
 | 
			
		||||
    dialog.setInformativeText(
 | 
			
		||||
            "Will copy "+configDirMirage.path()+" → "+configDirMoment.path()
 | 
			
		||||
            +"\nWill copy "+dataDirMirage.path()+" → "+dataDirMoment.path());
 | 
			
		||||
    dialog.addButton(QMessageBox::Yes);
 | 
			
		||||
    dialog.addButton(QMessageBox::No);
 | 
			
		||||
    dialog.addButton(QMessageBox::Cancel);
 | 
			
		||||
    int result = dialog.exec();
 | 
			
		||||
    if (result == QMessageBox::Yes) {
 | 
			
		||||
        qWarning("Migrating config and data from Mirage");
 | 
			
		||||
        if (! configDirMoment.mkpath(".")) {
 | 
			
		||||
            qFatal("Could not create config directory");
 | 
			
		||||
            exit(EXIT_FAILURE);
 | 
			
		||||
        }
 | 
			
		||||
        if (! dataDirMoment.mkpath(".")) {
 | 
			
		||||
            qFatal("Could not create data directory");
 | 
			
		||||
            exit(EXIT_FAILURE);
 | 
			
		||||
        }
 | 
			
		||||
        migrateFile(configDirMirage, configDirMoment, "settings.py");
 | 
			
		||||
        migrateFile(configDirMirage, configDirMoment, "settings.gui.json");
 | 
			
		||||
        migrateFile(configDirMirage, configDirMoment, "accounts.json");
 | 
			
		||||
        migrateFile(dataDirMirage, dataDirMoment, "history.json");
 | 
			
		||||
        migrateFile(dataDirMirage, dataDirMoment, "state.json");
 | 
			
		||||
        migrateShallowDirectory(dataDirMirage, dataDirMoment, "themes");
 | 
			
		||||
        migrateShallowDirectory(dataDirMirage, dataDirMoment, "encryption");
 | 
			
		||||
    } else if (result == QMessageBox::No) {
 | 
			
		||||
        // Nothing to do. Proceed with starting the app
 | 
			
		||||
        qWarning("Not migrating");
 | 
			
		||||
        return;
 | 
			
		||||
    } else {
 | 
			
		||||
        // Neither "Yes" nor "No" was chosen.
 | 
			
		||||
        // We can't know what the user wants. Just quit.
 | 
			
		||||
        qWarning("Quitting. You can decide about migration next time.");
 | 
			
		||||
        exit(EXIT_SUCCESS);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool shouldMigrateFromMirage() {
 | 
			
		||||
    QString genericConfig = QStandardPaths::writableLocation(
 | 
			
		||||
            QStandardPaths::GenericConfigLocation);
 | 
			
		||||
    QString genericData = QStandardPaths::writableLocation(
 | 
			
		||||
            QStandardPaths::GenericDataLocation);
 | 
			
		||||
 | 
			
		||||
    // Check whether Moment config already exists
 | 
			
		||||
    {
 | 
			
		||||
        QString customConfigDirMoment(
 | 
			
		||||
            qEnvironmentVariable("MOMENT_CONFIG_DIR")
 | 
			
		||||
        );
 | 
			
		||||
        if (! customConfigDirMoment.isEmpty()) {
 | 
			
		||||
            // MOMENT_CONFIG_DIR is set.
 | 
			
		||||
            // Moment would definitely use this as the config directory.
 | 
			
		||||
            if (QDir(customConfigDirMoment).exists())
 | 
			
		||||
                // But it already exists.
 | 
			
		||||
                // So this is not the first time Moment was started.
 | 
			
		||||
                return false;
 | 
			
		||||
        } else {
 | 
			
		||||
            // No MOMENT_CONFIG_DIR, so check the default config directory.
 | 
			
		||||
            if (QDir(genericConfig + "/moment").exists())
 | 
			
		||||
                // The default config folder exists.
 | 
			
		||||
                // So this is not the first time Moment was started.
 | 
			
		||||
                return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check whether Moment data already exists
 | 
			
		||||
    {
 | 
			
		||||
        QString customDataDirMoment(qEnvironmentVariable("MOMENT_DATA_DIR"));
 | 
			
		||||
        if (! customDataDirMoment.isEmpty()) {
 | 
			
		||||
            // MOMENT_DATA_DIR is set.
 | 
			
		||||
            // Moment would definitely use this as the data directory.
 | 
			
		||||
            if (QDir(customDataDirMoment).exists())
 | 
			
		||||
                // But it already exists.
 | 
			
		||||
                // So this is not the first time Moment was started.
 | 
			
		||||
                return false;
 | 
			
		||||
        } else {
 | 
			
		||||
            // No MOMENT_DATA_DIR, so check the default data directory.
 | 
			
		||||
            if (QDir(genericData + "/moment").exists())
 | 
			
		||||
                // The default data folder exists.
 | 
			
		||||
                // So this is not the first time Moment was started.
 | 
			
		||||
                return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check whether Mirage config exists
 | 
			
		||||
    {
 | 
			
		||||
        QString customConfigDirMirage(
 | 
			
		||||
            qEnvironmentVariable("MIRAGE_CONFIG_DIR")
 | 
			
		||||
        );
 | 
			
		||||
        if (! customConfigDirMirage.isEmpty()) {
 | 
			
		||||
            // MIRAGE_CONFIG_DIR is set.
 | 
			
		||||
            // Mirage would definitely use this as the config directory.
 | 
			
		||||
            if (! QDir(customConfigDirMirage).exists())
 | 
			
		||||
                // But this directory does not exist.
 | 
			
		||||
                // So there is nowhere to migrate from.
 | 
			
		||||
                return false;
 | 
			
		||||
        } else {
 | 
			
		||||
            // No MIRAGE_CONFIG_DIR, so check the default config directory.
 | 
			
		||||
            // Check /matrix-mirage (Debian) first, since it is more specific
 | 
			
		||||
            if (! QDir(genericConfig + "/matrix-mirage").exists())
 | 
			
		||||
                // Default Debian config folder does not exist,
 | 
			
		||||
                // so check whether the normal /mirage exists
 | 
			
		||||
                if (! QDir(genericConfig + "/mirage").exists())
 | 
			
		||||
                    // No, neither /matrix-mirage nor /mirage exist.
 | 
			
		||||
                    // So there is nowhere to migrate from.
 | 
			
		||||
                    return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // We found out that there is no Moment config dir nor Moment data dir,
 | 
			
		||||
    // but there is a Mirage config dir which can be migrated from.
 | 
			
		||||
    // We could also check for Mirage data dir but it doesn't really matter.
 | 
			
		||||
    // User should definitely be prompted for migration.
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void tryMigrateFromMirage() {
 | 
			
		||||
    QString genericConfig = QStandardPaths::writableLocation(
 | 
			
		||||
            QStandardPaths::GenericConfigLocation);
 | 
			
		||||
    QString genericData = QStandardPaths::writableLocation(
 | 
			
		||||
            QStandardPaths::GenericDataLocation);
 | 
			
		||||
 | 
			
		||||
    QDir configDirMoment, configDirMirage, dataDirMoment, dataDirMirage;
 | 
			
		||||
 | 
			
		||||
    QString customConfigDirMoment(qEnvironmentVariable("MOMENT_CONFIG_DIR"));
 | 
			
		||||
    configDirMoment = QDir(customConfigDirMoment.isEmpty()
 | 
			
		||||
            ? genericConfig + "/moment" : customConfigDirMoment);
 | 
			
		||||
 | 
			
		||||
    QString customDataDirMoment(qEnvironmentVariable("MOMENT_DATA_DIR"));
 | 
			
		||||
    dataDirMoment = QDir(customDataDirMoment.isEmpty()
 | 
			
		||||
            ? genericData + "/moment" : customDataDirMoment);
 | 
			
		||||
 | 
			
		||||
    QString customConfigDirMirage(qEnvironmentVariable("MIRAGE_CONFIG_DIR"));
 | 
			
		||||
    if (! customConfigDirMirage.isEmpty()) {
 | 
			
		||||
        // MIRAGE_CONFIG_DIR is set.
 | 
			
		||||
        // Mirage would definitely use this as the config directory.
 | 
			
		||||
        // So this is where we should migrate from.
 | 
			
		||||
        configDirMirage = QDir(customConfigDirMirage);
 | 
			
		||||
    } else {
 | 
			
		||||
        // No MIRAGE_CONFIG_DIR.
 | 
			
		||||
        // Check if Mirage default config directory exists
 | 
			
		||||
        // Check /matrix-mirage (Debian) first
 | 
			
		||||
        QDir dirDeb(genericConfig + "/matrix-mirage");
 | 
			
		||||
        if (dirDeb.exists()) {
 | 
			
		||||
            // Default Debian config dir exists.
 | 
			
		||||
            // So this is where we should migrate from.
 | 
			
		||||
            configDirMirage = dirDeb;
 | 
			
		||||
        } else {
 | 
			
		||||
            // No /matrix-mirage found, so check /mirage
 | 
			
		||||
            QDir dir(genericConfig + "/mirage");
 | 
			
		||||
            if (dir.exists())
 | 
			
		||||
                // Default config dir exists.
 | 
			
		||||
                // So this is where we should migrate from.
 | 
			
		||||
                configDirMirage = dir;
 | 
			
		||||
            else
 | 
			
		||||
                // No Mirage config dir found.
 | 
			
		||||
                // Do not migrate.
 | 
			
		||||
                return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    QString customDataDirMirage(qEnvironmentVariable("MIRAGE_DATA_DIR"));
 | 
			
		||||
    if (! customDataDirMirage.isEmpty()) {
 | 
			
		||||
        // MIRAGE_DATA_DIR is set.
 | 
			
		||||
        // Mirage would definitely use this as the data directory.
 | 
			
		||||
        // So this is where we should migrate from.
 | 
			
		||||
        dataDirMirage = QDir(customDataDirMirage);
 | 
			
		||||
    } else {
 | 
			
		||||
        // No MIRAGE_DATA_DIR.
 | 
			
		||||
        // Check if Mirage default data directory exists
 | 
			
		||||
        // Check /matrix-mirage (Debian) first
 | 
			
		||||
        QDir dirDeb(genericData + "/matrix-mirage");
 | 
			
		||||
        if (dirDeb.exists()) {
 | 
			
		||||
            // Default Debian data dir exists.
 | 
			
		||||
            // So this is where we should migrate from.
 | 
			
		||||
            dataDirMirage = dirDeb;
 | 
			
		||||
        } else {
 | 
			
		||||
            // No /matrix-mirage found, so check /mirage
 | 
			
		||||
            QDir dir(genericData + "/mirage");
 | 
			
		||||
            if (dir.exists())
 | 
			
		||||
                // Default data dir exists.
 | 
			
		||||
                // So this is where we should migrate from.
 | 
			
		||||
                dataDirMirage = dir;
 | 
			
		||||
            else
 | 
			
		||||
                // No Mirage data dir found.
 | 
			
		||||
                // Do not migrate.
 | 
			
		||||
                return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    offerMigrateFromMirage(
 | 
			
		||||
        configDirMoment, configDirMirage, dataDirMoment, dataDirMirage
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool setLockFile(QString configPath) {
 | 
			
		||||
 | 
			
		||||
    QDir settingsFolder(configPath);
 | 
			
		||||
@@ -130,13 +362,19 @@ int main(int argc, char *argv[]) {
 | 
			
		||||
    qInstallMessageHandler(loggingHandler);
 | 
			
		||||
 | 
			
		||||
    // Define some basic info about the app before creating the QApplication
 | 
			
		||||
    QApplication::setOrganizationName("mirage");
 | 
			
		||||
    QApplication::setApplicationName("mirage");
 | 
			
		||||
    QApplication::setApplicationDisplayName("Mirage");
 | 
			
		||||
    QApplication::setApplicationVersion("0.7.2");
 | 
			
		||||
    QApplication::setOrganizationName("moment");
 | 
			
		||||
    QApplication::setApplicationName("moment");
 | 
			
		||||
    QApplication::setApplicationDisplayName("Moment");
 | 
			
		||||
    QApplication::setApplicationVersion("0.7.3");
 | 
			
		||||
    QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
 | 
			
		||||
 | 
			
		||||
    QString customConfigDir(qEnvironmentVariable("MIRAGE_CONFIG_DIR"));
 | 
			
		||||
    // app needs to be constructed before attempting to migrate
 | 
			
		||||
    // because migrate displays a popup dialog
 | 
			
		||||
    QApplication app(argc, argv);
 | 
			
		||||
 | 
			
		||||
    if (shouldMigrateFromMirage()) tryMigrateFromMirage();
 | 
			
		||||
 | 
			
		||||
    QString customConfigDir(qEnvironmentVariable("MOMENT_CONFIG_DIR"));
 | 
			
		||||
    QString settingsFolder(
 | 
			
		||||
        customConfigDir.isEmpty() ?
 | 
			
		||||
        QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)
 | 
			
		||||
@@ -144,8 +382,8 @@ int main(int argc, char *argv[]) {
 | 
			
		||||
        customConfigDir
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Attempt to create a lockfile in the settings folder
 | 
			
		||||
    if (! setLockFile(settingsFolder)) return EXIT_SUCCESS;
 | 
			
		||||
    QApplication app(argc, argv);
 | 
			
		||||
 | 
			
		||||
    // Register handlers for quit signals, e.g. SIGINT/Ctrl-C in unix terminals
 | 
			
		||||
    signal(SIGINT, onExitSignal);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										561
									
								
								src/themes/Foliage.qpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,561 @@
 | 
			
		||||
// vim: syntax=qml
 | 
			
		||||
 | 
			
		||||
// Base variables
 | 
			
		||||
 | 
			
		||||
real uiScale: window.settings.General.zoom
 | 
			
		||||
 | 
			
		||||
int minimumSupportedWidth:  240 * uiScale
 | 
			
		||||
int minimumSupportedHeight: 120 * uiScale
 | 
			
		||||
int contentIsWideAbove:     472 * uiScale
 | 
			
		||||
 | 
			
		||||
int baseElementsHeight:       36 * uiScale
 | 
			
		||||
int spacing:                  12 * uiScale
 | 
			
		||||
int radius:                   4 * uiScale
 | 
			
		||||
int animationDuration:        100
 | 
			
		||||
real loadingElementsOpacity:  0.8
 | 
			
		||||
real disabledElementsOpacity: 0.3
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
fontSize:
 | 
			
		||||
    int smaller: 13 * uiScale
 | 
			
		||||
    int small:   13 * uiScale
 | 
			
		||||
    int normal:  16 * uiScale
 | 
			
		||||
    int big:     20 * uiScale
 | 
			
		||||
    int bigger:  32 * uiScale
 | 
			
		||||
    int biggest: 48 * uiScale
 | 
			
		||||
 | 
			
		||||
fontFamily:
 | 
			
		||||
    string sans: "Roboto"
 | 
			
		||||
    string mono: "Hack"
 | 
			
		||||
    // Fonts are comma-separated.
 | 
			
		||||
    // For example, if you want colorful emoji, you could do:
 | 
			
		||||
    // string sans: "Roboto,Joypixels"
 | 
			
		||||
    // string mono: "Hack,Joypixels"
 | 
			
		||||
 | 
			
		||||
colors:
 | 
			
		||||
    int hue: 200
 | 
			
		||||
 | 
			
		||||
    real intensity:               1.0
 | 
			
		||||
    real coloredTextIntensity:    intensity * 71
 | 
			
		||||
    real dimColoredTextIntensity: intensity * 60
 | 
			
		||||
 | 
			
		||||
    int saturation:               40
 | 
			
		||||
    int bgSaturation:             saturation
 | 
			
		||||
    int coloredTextSaturation:    saturation + 20
 | 
			
		||||
    int dimColoredTextSaturation: saturation
 | 
			
		||||
 | 
			
		||||
    real opacity: 0.7
 | 
			
		||||
 | 
			
		||||
    color weakBackground:
 | 
			
		||||
        hsluv(hue, bgSaturation, intensity * 2.5, opacity)
 | 
			
		||||
    color mediumBackground:
 | 
			
		||||
        hsluv(hue, bgSaturation, intensity * 7, opacity)
 | 
			
		||||
    color strongBackground:
 | 
			
		||||
        hsluv(hue, bgSaturation * 2, intensity, opacity)
 | 
			
		||||
 | 
			
		||||
    color accentBackground:    hsluv(hue, saturation, intensity * 40, 1)
 | 
			
		||||
    color accentElement:       hsluv(hue, saturation * 1.5, intensity * 52, 1)
 | 
			
		||||
    color strongAccentElement: hsluv(hue, saturation * 1.5, intensity * 72, 1)
 | 
			
		||||
 | 
			
		||||
    color positiveBackground:
 | 
			
		||||
        hsluv(155, saturation * 1.5, intensity * 65, 1)
 | 
			
		||||
 | 
			
		||||
    color middleBackground:
 | 
			
		||||
        hsluv(60, saturation * 1.5, intensity * 65, 1)
 | 
			
		||||
 | 
			
		||||
    color negativeBackground:
 | 
			
		||||
        hsluv(0, saturation * 1.5, intensity * 54, 1)
 | 
			
		||||
 | 
			
		||||
    color alertBackground: negativeBackground
 | 
			
		||||
 | 
			
		||||
    color brightText:  hsluv(0, 0, intensity * 100)
 | 
			
		||||
    color text:        hsluv(0, 0, intensity * 85)
 | 
			
		||||
    color halfDimText: hsluv(0, 0, intensity * 72)
 | 
			
		||||
    color dimText:     hsluv(0, 0, intensity * 60)
 | 
			
		||||
 | 
			
		||||
    color positiveText: hsluv(155, coloredTextSaturation, coloredTextIntensity)
 | 
			
		||||
    color warningText:  hsluv(60, coloredTextSaturation, coloredTextIntensity)
 | 
			
		||||
    color errorText:    hsluv(0, coloredTextSaturation, coloredTextIntensity)
 | 
			
		||||
    color accentText:   hsluv(hue, coloredTextSaturation, coloredTextIntensity)
 | 
			
		||||
 | 
			
		||||
    color link: hsluv(hue, coloredTextSaturation, coloredTextIntensity)
 | 
			
		||||
    color code: hsluv(hue + 10, coloredTextSaturation, coloredTextIntensity)
 | 
			
		||||
 | 
			
		||||
    // Example of an animation, set running: true to enable
 | 
			
		||||
    NumberAnimation on hue
 | 
			
		||||
        running:  false
 | 
			
		||||
        from:     0
 | 
			
		||||
        to:       360
 | 
			
		||||
        duration: 10000
 | 
			
		||||
        loops:    Animation.Infinite
 | 
			
		||||
 | 
			
		||||
icons:
 | 
			
		||||
    string preferredPack: "thin"
 | 
			
		||||
 | 
			
		||||
    // "transparent" to disable colorizing
 | 
			
		||||
    color colorize:         hsluv(0, 0, colors.intensity * 90)
 | 
			
		||||
    color disabledColorize: "white"
 | 
			
		||||
 | 
			
		||||
    int smallDimension: 16 * uiScale
 | 
			
		||||
    int dimension: 22 * uiScale
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Generic UI controls
 | 
			
		||||
 | 
			
		||||
controls:
 | 
			
		||||
    scrollBar:
 | 
			
		||||
        int width: theme.spacing
 | 
			
		||||
 | 
			
		||||
        color track: colors.strongBackground
 | 
			
		||||
 | 
			
		||||
        color slider:        colors.accentElement
 | 
			
		||||
        color hoveredSlider: colors.accentElement
 | 
			
		||||
        color pressedSlider: colors.strongAccentElement
 | 
			
		||||
 | 
			
		||||
        int sliderPadding: 2
 | 
			
		||||
        int sliderRadius:  theme.radius
 | 
			
		||||
 | 
			
		||||
    box:
 | 
			
		||||
        int defaultWidth: minimumSupportedWidth
 | 
			
		||||
        color background: colors.mediumBackground
 | 
			
		||||
        int radius:       theme.radius
 | 
			
		||||
 | 
			
		||||
    popup:
 | 
			
		||||
        int defaultWidth:    minimumSupportedWidth * 1.75
 | 
			
		||||
        color background:    colors.mediumBackground
 | 
			
		||||
        color opaqueBackground: hsluv(
 | 
			
		||||
            colors.hue,
 | 
			
		||||
            colors.bgSaturation,
 | 
			
		||||
            colors.intensity * 7,
 | 
			
		||||
            1
 | 
			
		||||
        )
 | 
			
		||||
        color windowOverlay: hsluv(0, 0, 0, 0.7)
 | 
			
		||||
 | 
			
		||||
    header:
 | 
			
		||||
        color background: colors.strongBackground
 | 
			
		||||
 | 
			
		||||
    button:
 | 
			
		||||
        color background:       colors.strongBackground
 | 
			
		||||
        color text:             colors.text
 | 
			
		||||
        color focusedBorder:    colors.accentElement
 | 
			
		||||
        int focusedBorderWidth: 2
 | 
			
		||||
 | 
			
		||||
        color hoveredOverlay: hsluv(0, 0, 50, 0.2)
 | 
			
		||||
        color pressedOverlay: hsluv(0, 0, 50, 0.5)
 | 
			
		||||
        color checkedOverlay: colors.accentBackground
 | 
			
		||||
 | 
			
		||||
    tab:
 | 
			
		||||
        color text:                controls.button.text
 | 
			
		||||
        color background:          controls.button.background
 | 
			
		||||
        color alternateBackground: hsluv(
 | 
			
		||||
            colors.hue,
 | 
			
		||||
            colors.bgSaturation * 1.25,
 | 
			
		||||
            colors.intensity * 4,
 | 
			
		||||
            Math.max(0.6, colors.opacity)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        color bottomLine:       background
 | 
			
		||||
        color focusedBorder:    colors.accentElement
 | 
			
		||||
        int focusedBorderWidth: 1
 | 
			
		||||
 | 
			
		||||
        color hoveredOverlay: controls.button.hoveredOverlay
 | 
			
		||||
        color pressedOverlay: controls.button.pressedOverlay
 | 
			
		||||
        color checkedOverlay: controls.button.checkedOverlay
 | 
			
		||||
 | 
			
		||||
    menu:
 | 
			
		||||
        color background: hsluv(
 | 
			
		||||
            colors.hue,
 | 
			
		||||
            colors.bgSaturation * 2,
 | 
			
		||||
            colors.intensity,
 | 
			
		||||
            Math.max(0.9, colors.opacity),
 | 
			
		||||
        )
 | 
			
		||||
        color border:     "black"
 | 
			
		||||
        real borderWidth: 2
 | 
			
		||||
 | 
			
		||||
    menuItem:
 | 
			
		||||
        color background: "transparent"
 | 
			
		||||
        color text:       controls.button.text
 | 
			
		||||
 | 
			
		||||
        color hoveredOverlay: controls.button.hoveredOverlay
 | 
			
		||||
        color pressedOverlay: controls.button.hoveredOverlay
 | 
			
		||||
        color checkedOverlay: controls.button.hoveredOverlay
 | 
			
		||||
 | 
			
		||||
    checkBox:
 | 
			
		||||
        color checkIconColorize: colors.accentElement
 | 
			
		||||
        color boxBackground:     controls.button.background
 | 
			
		||||
        int boxSize:             24 * uiScale
 | 
			
		||||
 | 
			
		||||
        color boxBorder:        "black"
 | 
			
		||||
        color boxHoveredBorder: colors.accentElement
 | 
			
		||||
        color boxPressedBorder: colors.strongAccentElement
 | 
			
		||||
 | 
			
		||||
        color text:     controls.button.text
 | 
			
		||||
        color subtitle: colors.dimText
 | 
			
		||||
 | 
			
		||||
    listView:
 | 
			
		||||
        color highlight: hsluv(
 | 
			
		||||
            colors.hue,
 | 
			
		||||
            colors.bgSaturation * 2,
 | 
			
		||||
            colors.intensity * 1,
 | 
			
		||||
            colors.opacity / 1.5,
 | 
			
		||||
        )
 | 
			
		||||
        color highlightBorder: colors.strongAccentElement
 | 
			
		||||
        int highlightBorderThickness: 1
 | 
			
		||||
 | 
			
		||||
    textField:
 | 
			
		||||
        color background:        colors.strongBackground
 | 
			
		||||
        color focusedBackground: background
 | 
			
		||||
 | 
			
		||||
        int borderWidth:     1
 | 
			
		||||
        color border:        "transparent"
 | 
			
		||||
        color focusedBorder: colors.accentElement
 | 
			
		||||
        color errorBorder:   colors.negativeBackground
 | 
			
		||||
 | 
			
		||||
        color text:            colors.text
 | 
			
		||||
        color focusedText:     colors.text
 | 
			
		||||
        color placeholderText: colors.dimText
 | 
			
		||||
 | 
			
		||||
    textArea:
 | 
			
		||||
        color background: colors.strongBackground
 | 
			
		||||
 | 
			
		||||
        int borderWidth:     1
 | 
			
		||||
        color border:        "transparent"
 | 
			
		||||
        color focusedBorder: colors.accentElement
 | 
			
		||||
        color errorBorder:   colors.negativeBackground
 | 
			
		||||
 | 
			
		||||
        color text:            colors.text
 | 
			
		||||
        color placeholderText: controls.textField.placeholderText
 | 
			
		||||
 | 
			
		||||
    toolTip:
 | 
			
		||||
        color background: colors.strongBackground
 | 
			
		||||
        color text:       colors.text
 | 
			
		||||
        color border:     "black"
 | 
			
		||||
        int borderWidth:  2
 | 
			
		||||
 | 
			
		||||
    progressBar:
 | 
			
		||||
        int height:             Math.max(2, spacing / 2)
 | 
			
		||||
        color background:       colors.strongBackground
 | 
			
		||||
        color foreground:       colors.accentElement
 | 
			
		||||
        color pausedForeground: colors.middleBackground
 | 
			
		||||
        color errorForeground:  colors.negativeBackground
 | 
			
		||||
 | 
			
		||||
    circleProgressBar:
 | 
			
		||||
        int thickness:          Math.max(2, spacing / 2)
 | 
			
		||||
        color background:       colors.strongBackground
 | 
			
		||||
        color foreground:       colors.accentElement
 | 
			
		||||
        color errorForeground:  colors.negativeBackground
 | 
			
		||||
        color text:             colors.text
 | 
			
		||||
        real indeterminateSpan: 0.5  // 0-1
 | 
			
		||||
 | 
			
		||||
    slider:
 | 
			
		||||
        int radius:       2
 | 
			
		||||
        int height:       controls.progressBar.height
 | 
			
		||||
        color background: controls.progressBar.background
 | 
			
		||||
        color foreground: controls.progressBar.foreground
 | 
			
		||||
 | 
			
		||||
        handle:
 | 
			
		||||
            int size: 20
 | 
			
		||||
            color inside: hsluv(0, 0, 90)
 | 
			
		||||
            color pressedInside: "white"
 | 
			
		||||
            color border: "black"
 | 
			
		||||
            color pressedBorder: colors.strongAccentElement
 | 
			
		||||
 | 
			
		||||
    avatar:
 | 
			
		||||
        int size:        baseElementsHeight
 | 
			
		||||
        int compactSize: baseElementsHeight / 2
 | 
			
		||||
        int radius:      theme.radius
 | 
			
		||||
 | 
			
		||||
        hoveredImage:
 | 
			
		||||
            int size: 192
 | 
			
		||||
            color background: hsluv(0, 0, 0, 0.4)
 | 
			
		||||
 | 
			
		||||
        background:
 | 
			
		||||
            int saturation: colors.saturation
 | 
			
		||||
            int lightness:  Math.min(50, colors.intensity * 25)
 | 
			
		||||
            real opacity:   1.0
 | 
			
		||||
 | 
			
		||||
        letter:
 | 
			
		||||
            int saturation: colors.saturation + 20
 | 
			
		||||
            int lightness:  colors.intensity * 60
 | 
			
		||||
            real opacity:   1.0
 | 
			
		||||
 | 
			
		||||
    displayName:
 | 
			
		||||
        int saturation:    colors.coloredTextSaturation
 | 
			
		||||
        int lightness:     colors.coloredTextIntensity
 | 
			
		||||
        int dimSaturation: colors.dimColoredTextSaturation
 | 
			
		||||
        int dimLightness:  colors.dimColoredTextIntensity
 | 
			
		||||
 | 
			
		||||
    presence:
 | 
			
		||||
        color online:      colors.positiveBackground
 | 
			
		||||
        color unavailable: colors.middleBackground
 | 
			
		||||
        color offline:     hsluv(0, 0, 60, 1)
 | 
			
		||||
        color border:      "black"
 | 
			
		||||
        int borderWidth:   2 * uiScale
 | 
			
		||||
        real opacity:      1.0
 | 
			
		||||
        real radius:       6.0 * uiScale
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Specific interface parts
 | 
			
		||||
 | 
			
		||||
ui:
 | 
			
		||||
    // The background image can be an URL or local file path
 | 
			
		||||
    // (in the form file://<path>, e.g. file:///home/user/images/foo.png).
 | 
			
		||||
    // If not specified, the gradient will be shown instead.
 | 
			
		||||
    url image: "../../images/foliage.jpg"
 | 
			
		||||
 | 
			
		||||
    point gradientStart: Qt.point(0, 0)
 | 
			
		||||
    point gradientEnd:   Qt.point(window.width, window.height)
 | 
			
		||||
 | 
			
		||||
    color gradientStartColor:
 | 
			
		||||
        hsluv(colors.hue, 100, colors.intensity * 8)
 | 
			
		||||
    color gradientEndColor:
 | 
			
		||||
        hsluv(colors.hue + 50, 30, colors.intensity * 22)
 | 
			
		||||
 | 
			
		||||
    // To have a solid color instead,
 | 
			
		||||
    // set gradientStartColor and gradientEndColor to the same value, e.g.:
 | 
			
		||||
    // color gradientStartColor: hsluv(0, 0, 0, 0.5)
 | 
			
		||||
    // color gradientEndColor: hsluv(0, 0, 0, 0.5)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
mainPane:
 | 
			
		||||
    color background: "transparent"
 | 
			
		||||
 | 
			
		||||
    topBar:
 | 
			
		||||
        color background:       colors.strongBackground
 | 
			
		||||
        color nameVersionLabel: colors.text
 | 
			
		||||
 | 
			
		||||
    accountBar:
 | 
			
		||||
        color background: colors.mediumBackground
 | 
			
		||||
 | 
			
		||||
        account:
 | 
			
		||||
            color selectedBackground:       colors.accentBackground
 | 
			
		||||
            real selectedBackgroundOpacity: 0.3
 | 
			
		||||
            color selectedBorder:           colors.strongAccentElement
 | 
			
		||||
            int selectedBorderSize:         1
 | 
			
		||||
 | 
			
		||||
            unreadIndicator:
 | 
			
		||||
                color background: colors.strongBackground
 | 
			
		||||
                color text:       colors.accentText
 | 
			
		||||
                bool bold:        false
 | 
			
		||||
                color border:     Qt.darker(text, 2)
 | 
			
		||||
                int borderWidth:  1
 | 
			
		||||
                int radius:       theme.radius / 2
 | 
			
		||||
 | 
			
		||||
                color highlightBackground: colors.strongBackground
 | 
			
		||||
                color highlightText:       colors.errorText
 | 
			
		||||
                bool highlightBold:        false
 | 
			
		||||
                color highlightBorder:     Qt.darker(highlightText, 2)
 | 
			
		||||
                int highlightBorderWidth:  1
 | 
			
		||||
                int highlightRadius:       theme.radius / 2
 | 
			
		||||
 | 
			
		||||
    listView:
 | 
			
		||||
        color background:    colors.mediumBackground
 | 
			
		||||
        real offlineOpacity: 0.5
 | 
			
		||||
 | 
			
		||||
        account:
 | 
			
		||||
            real collapsedOpacity:     0.3
 | 
			
		||||
            color background:          "transparent"
 | 
			
		||||
            color name:                colors.text
 | 
			
		||||
 | 
			
		||||
            int avatarRadius:          controls.avatar.radius
 | 
			
		||||
            int collapsedAvatarRadius: controls.avatar.size / 2
 | 
			
		||||
 | 
			
		||||
        room:
 | 
			
		||||
            real leftRoomOpacity: 0.65
 | 
			
		||||
 | 
			
		||||
            color background:    "transparent"
 | 
			
		||||
            color name:          colors.text
 | 
			
		||||
            color unreadName:    colors.brightText
 | 
			
		||||
            color lastEventDate: colors.halfDimText
 | 
			
		||||
 | 
			
		||||
            color subtitle:      colors.dimText
 | 
			
		||||
            color subtitleQuote: chat.message.quote
 | 
			
		||||
 | 
			
		||||
            int avatarRadius:          controls.avatar.radius
 | 
			
		||||
            int collapsedAvatarRadius: controls.avatar.radius
 | 
			
		||||
 | 
			
		||||
            unreadIndicator:
 | 
			
		||||
                color background: colors.strongBackground
 | 
			
		||||
                color text:       colors.accentText
 | 
			
		||||
                bool bold:        false
 | 
			
		||||
                color border:     Qt.darker(text, 2)
 | 
			
		||||
                int borderWidth:  1
 | 
			
		||||
                int radius:       theme.radius / 2
 | 
			
		||||
 | 
			
		||||
                color highlightBackground: colors.strongBackground
 | 
			
		||||
                color highlightText:       colors.errorText
 | 
			
		||||
                bool highlightBold:        false
 | 
			
		||||
                color highlightBorder:     Qt.darker(highlightText, 2)
 | 
			
		||||
                int highlightBorderWidth:  1
 | 
			
		||||
                int highlightRadius:       theme.radius / 2
 | 
			
		||||
 | 
			
		||||
    bottomBar:
 | 
			
		||||
        color background:               "transparent"
 | 
			
		||||
        color settingsButtonBackground: colors.strongBackground
 | 
			
		||||
        color filterFieldBackground:    colors.strongBackground
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
chat:
 | 
			
		||||
    roomHeader:
 | 
			
		||||
        color background: controls.header.background
 | 
			
		||||
        color name:       colors.text
 | 
			
		||||
        color topic:      colors.dimText
 | 
			
		||||
 | 
			
		||||
    roomPane:
 | 
			
		||||
        color background: "transparent"
 | 
			
		||||
 | 
			
		||||
        topBar:
 | 
			
		||||
            color background: colors.strongBackground
 | 
			
		||||
 | 
			
		||||
        listView:
 | 
			
		||||
            color background: colors.mediumBackground
 | 
			
		||||
 | 
			
		||||
            member:
 | 
			
		||||
                real invitedOpacity: 0.5
 | 
			
		||||
 | 
			
		||||
                color background: "transparent"
 | 
			
		||||
                color name:       colors.text
 | 
			
		||||
                color subtitle:   colors.dimText
 | 
			
		||||
 | 
			
		||||
                color adminIcon:     hsluv(60, colors.saturation * 2.25, 60)
 | 
			
		||||
                color moderatorIcon: adminIcon
 | 
			
		||||
                color invitedIcon:   hsluv(0, colors.saturation * 2.25, 60)
 | 
			
		||||
 | 
			
		||||
        roomSettings:
 | 
			
		||||
            color background: colors.mediumBackground
 | 
			
		||||
 | 
			
		||||
        bottomBar:
 | 
			
		||||
            color background: colors.strongBackground
 | 
			
		||||
 | 
			
		||||
            inviteButton:
 | 
			
		||||
                color background: "transparent"
 | 
			
		||||
 | 
			
		||||
            filterMembers:
 | 
			
		||||
                color background: "transparent"
 | 
			
		||||
 | 
			
		||||
    eventList:
 | 
			
		||||
        color background: "transparent"
 | 
			
		||||
 | 
			
		||||
    message:
 | 
			
		||||
        int avatarSize:          56 * uiScale
 | 
			
		||||
        int collapsedAvatarSize: 32 * uiScale
 | 
			
		||||
        int avatarRadius:        controls.avatar.radius
 | 
			
		||||
 | 
			
		||||
        int radius:            theme.radius
 | 
			
		||||
        int horizontalSpacing: theme.spacing / 1.25
 | 
			
		||||
        int verticalSpacing:   theme.spacing / 1.75
 | 
			
		||||
 | 
			
		||||
        color focusedHighlight:       colors.accentBackground
 | 
			
		||||
        real focusedHighlightOpacity: 0.4
 | 
			
		||||
 | 
			
		||||
        color background:        colors.weakBackground
 | 
			
		||||
        color ownBackground:     colors.mediumBackground
 | 
			
		||||
        color checkedBackground: colors.accentBackground
 | 
			
		||||
 | 
			
		||||
        color body:        colors.text
 | 
			
		||||
        color date:        colors.dimText
 | 
			
		||||
        color localEcho:   colors.dimText
 | 
			
		||||
        color readCounter: colors.accentText
 | 
			
		||||
 | 
			
		||||
        color redactedBody: colors.dimText
 | 
			
		||||
 | 
			
		||||
        color noticeBody:    colors.halfDimText
 | 
			
		||||
        int noticeLineWidth: 1 * uiScale
 | 
			
		||||
 | 
			
		||||
        color quote: hsluv(
 | 
			
		||||
            135, colors.coloredTextSaturation, colors.coloredTextIntensity,
 | 
			
		||||
        )
 | 
			
		||||
        color link:  colors.link
 | 
			
		||||
        color code:  colors.code
 | 
			
		||||
 | 
			
		||||
        string styleSheet:
 | 
			
		||||
            "* { white-space: pre-wrap }" +
 | 
			
		||||
            "a { color: " + link  + " }" +
 | 
			
		||||
            "p { margin-top: 0 }" +
 | 
			
		||||
 | 
			
		||||
            "code { font-family: " + fontFamily.mono + "; " +
 | 
			
		||||
                   "color: "       + code            + " }" +
 | 
			
		||||
 | 
			
		||||
            "h1, h2, h3 { font-weight: normal }" +
 | 
			
		||||
            "h1 { font-size: " + fontSize.biggest + "px }" +
 | 
			
		||||
            "h2 { font-size: " + fontSize.bigger + "px }" +
 | 
			
		||||
            "h3 { font-size: " + fontSize.big + "px }" +
 | 
			
		||||
            "h4 { font-size: " + fontSize.normal + "px }" +
 | 
			
		||||
            "h5 { font-size: " + fontSize.small + "px }" +
 | 
			
		||||
            "h6 { font-size: " + fontSize.smaller + "px }" +
 | 
			
		||||
 | 
			
		||||
            "table { margin-top: " + theme.spacing + "px; " +
 | 
			
		||||
            "        margin-bottom: " + theme.spacing + "px }" +
 | 
			
		||||
 | 
			
		||||
            "td { padding-left: " + theme.spacing / 2 + "px; " +
 | 
			
		||||
            "     padding-right: " + theme.spacing / 2 + "px; " +
 | 
			
		||||
            "     padding-top: " + theme.spacing / 4 + "px; " +
 | 
			
		||||
            "     padding-bottom: " + theme.spacing / 4 + "px } " +
 | 
			
		||||
 | 
			
		||||
            "li { margin-top: " + theme.spacing / 2 + "px; " +
 | 
			
		||||
            "     margin-bottom: " + theme.spacing / 2 + "px; }" +
 | 
			
		||||
 | 
			
		||||
            ".sender { margin-bottom: " + spacing / 2 + " }" +
 | 
			
		||||
            ".quote  { color: " + quote + " }" +
 | 
			
		||||
 | 
			
		||||
            ".mention { text-decoration: none; }" +
 | 
			
		||||
            ".room-id-mention, .room-alias-mention { font-weight: bold; }"
 | 
			
		||||
 | 
			
		||||
        string styleInclude:
 | 
			
		||||
            '<style type"text/css">\n' + styleSheet + '\n</style>\n'
 | 
			
		||||
 | 
			
		||||
        real thumbnailCheckedOverlayOpacity: 0.4
 | 
			
		||||
 | 
			
		||||
    daybreak:
 | 
			
		||||
        color background: colors.mediumBackground
 | 
			
		||||
        color text:       colors.text
 | 
			
		||||
        int radius:       theme.radius
 | 
			
		||||
 | 
			
		||||
    inviteBanner:
 | 
			
		||||
        color background: colors.mediumBackground
 | 
			
		||||
 | 
			
		||||
    leftBanner:
 | 
			
		||||
        color background: colors.mediumBackground
 | 
			
		||||
 | 
			
		||||
    unknownDevices:
 | 
			
		||||
        color background: colors.mediumBackground
 | 
			
		||||
 | 
			
		||||
    typingMembers:
 | 
			
		||||
        color background: hsluv(
 | 
			
		||||
            colors.hue, colors.saturation, colors.intensity * 9, 0.52
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    replyBar:
 | 
			
		||||
        color background: chat.typingMembers.background
 | 
			
		||||
 | 
			
		||||
    fileTransfer:
 | 
			
		||||
        color background: chat.typingMembers.background
 | 
			
		||||
 | 
			
		||||
    userAutoCompletion:
 | 
			
		||||
        color background:   chat.typingMembers.background
 | 
			
		||||
        int avatarsRadius:  controls.avatar.radius
 | 
			
		||||
        color displayNames: colors.text
 | 
			
		||||
        color userIds:      colors.dimText
 | 
			
		||||
 | 
			
		||||
    composer:
 | 
			
		||||
        color background: colors.strongBackground
 | 
			
		||||
 | 
			
		||||
        uploadButton:
 | 
			
		||||
            color background: "transparent"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
mediaPlayer:
 | 
			
		||||
    hoverPreview:
 | 
			
		||||
        int maxHeight: 192
 | 
			
		||||
 | 
			
		||||
    progress:
 | 
			
		||||
        int height:        8
 | 
			
		||||
        color background:  hsluv(0, 0, 0, 0.5)
 | 
			
		||||
 | 
			
		||||
    controls:
 | 
			
		||||
        int iconSize:          icons.dimension
 | 
			
		||||
        int volumeSliderWidth: 100
 | 
			
		||||
        int speedSliderWidth:  100
 | 
			
		||||
        color background:      hsluv(
 | 
			
		||||
            colors.hue, colors.saturation * 1.25, colors.intensity * 2, 0.85,
 | 
			
		||||
        )
 | 
			
		||||