diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index e6665f7b..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -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 diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index c0b84de9..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for the application or project -title: '' -labels: enhancement -assignees: '' - ---- - - diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index dfa4dfdc..00000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Question -about: Ask a question about the application or project -title: '' -labels: question -assignees: '' - ---- - - diff --git a/.gitignore b/.gitignore index 7292d91f..32769594 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ dist Makefile mirage mirage.pro.user +moment +moment.pro.user *.AppImage tags diff --git a/README.md b/README.md index 5685b65f..cd74adf7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # Moment -https://img.shields.io/gitlab/v/release/mx-moment/moment [![Latest release](https://img.shields.io/gitlab/v/release/mx-moment/moment)](https://gitlab.com/mx-moment/moment/-/releases) [![Built with matrix-nio](https://img.shields.io/badge/built%20with-matrix--nio-brightgreen)](https://github.com/poljar/matrix-nio) [![#moment-client:matrix.org](https://img.shields.io/matrix/moment-client:matrix.org?color=blueviolet)](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). -![Chat screenshot](docs/screenshots/01-chat.png?raw=true) +![Chat screenshot](docs/screenshots/m01-chat.png?raw=true) ## Currently Implemented Features @@ -96,10 +95,10 @@ see differences [here](docs/MIRAGEDIFF.md). ## Screenshots -![Sign-in](docs/screenshots/02-sign-in.png) -![Account settings](docs/screenshots/03-account-settings.png) -![Room creation](docs/screenshots/04-create-room.png) -![Chat](docs/screenshots/01-chat.png?raw=true) -![Main pane in small window](docs/screenshots/05-main-pane-small.png) -![Chat in small window](docs/screenshots/06-chat-small.png) -![Room pane in small window](docs/screenshots/07-room-pane-small.png) +![Sign-in](docs/screenshots/m02-sign-in.png) +![Account settings](docs/screenshots/m03-account-settings.png) +![Room creation](docs/screenshots/m04-create-room.png) +![Chat](docs/screenshots/m01-chat.png?raw=true) +![Main pane in small window](docs/screenshots/m05-main-pane-small.png) +![Chat in small window](docs/screenshots/m06-chat-small.png) +![Room pane in small window](docs/screenshots/m07-room-pane-small.png) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3640ac9b..2833fb7d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -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 diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 1791bef7..3b1ba12e 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -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 diff --git a/docs/INSTALL.md b/docs/INSTALL.md index cb1ea5be..e7ce42e2 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -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. diff --git a/docs/KEYBINDINGS.md b/docs/KEYBINDINGS.md index 477d7dc1..80d6a927 100644 --- a/docs/KEYBINDINGS.md +++ b/docs/KEYBINDINGS.md @@ -6,114 +6,114 @@ Keybindings as defined in [the default configuration file](src/config/settings.p Key | Function ------ | ------ -Ctrl + Alt + C | Toggle compact interface -Ctrl + + | Zoom in -Ctrl + - | Zoom out -Ctrl + = | Reset zoom -Alt + Shift + Left
Alt + Shift + H | Previous tab -Alt + Shift + Right
Alt + Shift + L | Next tab -Ctrl + Tab | Switch to the last opened page -Ctrl + H | Earlier page in history (page back) -Ctrl + L | Later page in history (page forward) -Ctrl + Alt + H | Toggle notifications, except highlights -Ctrl + Alt + N | Toggle notifications +Ctrl + Alt + C | Toggle compact interface +Ctrl + + | Zoom in +Ctrl + - | Zoom out +Ctrl + = | Reset zoom +Alt + Shift + Left
Alt + Shift + H | Previous tab +Alt + Shift + Right
Alt + Shift + L | Next tab +Ctrl + Tab | Switch to the last opened page +Ctrl + H | Earlier page in history (page back) +Ctrl + L | Later page in history (page forward) +Ctrl + Alt + H | Toggle notifications, except highlights +Ctrl + Alt + N | Toggle notifications F1 | QML developer console -Shift + F1 | Python debugger -Alt + F1 | Python remote debugger -Ctrl + Q | Quit Moment * +Shift + F1 | Python debugger +Alt + F1 | Python remote debugger +Ctrl + Q | Quit Moment * ## Scrolling bindings Key | Function ------ | ------ -Alt + Up
Alt + K | Scroll up -Alt + Down
Alt + J | Scroll down -Ctrl + Alt + Up
Ctrl + Alt + K
PgUp | Page up -Ctrl + Alt + Down
Ctrl + Alt + J
PgDown | Page down -Ctrl + Alt + Shift + Up
Ctrl + Alt + Shift + K
Home | Scroll to top -Ctrl + Alt + Shift + Down
Ctrl + Alt + Shift + J
End | Scroll to bottom +Alt + Up
Alt + K | Scroll up +Alt + Down
Alt + J | Scroll down +Ctrl + Alt + Up
Ctrl + Alt + K
PgUp | Page up +Ctrl + Alt + Down
Ctrl + Alt + J
PgDown | Page down +Ctrl + Alt + Shift + Up
Ctrl + Alt + Shift + K
Home | Scroll to top +Ctrl + Alt + Shift + Down
Ctrl + Alt + Shift + J
End | Scroll to bottom ## Account bindings Key | Function ------ | ------ -Alt + Shift + A | Add new account -Alt + O | Collapse current account -Alt + A | Current account settings -Alt + P | Current account context menu -Ctrl + Alt + U
Ctrl + Alt + A | Unavailable status -Ctrl + Alt + I | Invisible status -Ctrl + Alt + O | Offline status -Alt + Shift + N | Previous account -Alt + N | Next account -Ctrl + 1 | Switch to account 1 -Ctrl + 2 | Switch to account 2 -Ctrl + 3 | Switch to account 3 -Ctrl + 4 | Switch to account 4 -Ctrl + 5 | Switch to account 5 -Ctrl + 6 | Switch to account 6 -Ctrl + 7 | Switch to account 7 -Ctrl + 8 | Switch to account 8 -Ctrl + 9 | Switch to account 9 -Ctrl + 0 | Switch to account 10 +Alt + Shift + A | Add new account +Alt + O | Collapse current account +Alt + A | Current account settings +Alt + P | Current account context menu +Ctrl + Alt + U
Ctrl + Alt + A | Unavailable status +Ctrl + Alt + I | Invisible status +Ctrl + Alt + O | Offline status +Alt + Shift + N | Previous account +Alt + N | Next account +Ctrl + 1 | Switch to account 1 +Ctrl + 2 | Switch to account 2 +Ctrl + 3 | Switch to account 3 +Ctrl + 4 | Switch to account 4 +Ctrl + 5 | Switch to account 5 +Ctrl + 6 | Switch to account 6 +Ctrl + 7 | Switch to account 7 +Ctrl + 8 | Switch to account 8 +Ctrl + 9 | Switch to account 9 +Ctrl + 0 | Switch to account 10 ## Room bindings Key | Function ------ | ------ -Alt + C | Create a new room (start chat) -Alt + F | Focus filter -Alt + Shift + F | Clear filter -Alt + Shift + Up
Alt + Shift + K | Previous room -Alt + Shift + Down
Alt + Shift + J | Next room -Alt + Shift + U | Previous unread -Alt + U | Next unread -Ctrl + Shift + U | Oldest unread -Ctrl + U | Latest unread -Alt + Shift + M | Previous highlight -Alt + M | Next highlight -Ctrl + Shift + M | Oldest highlight -Ctrl + M | Latest highlight -Alt + 1 | Room number 1 in account -Alt + 2 | Room number 2 in account -Alt + 3 | Room number 3 in account -Alt + 4 | Room number 4 in account -Alt + 5 | Room number 5 in account -Alt + 6 | Room number 6 in account -Alt + 7 | Room number 7 in account -Alt + 8 | Room number 8 in account -Alt + 9 | Room number 9 in account -Alt + 0 | Room number 10 in account +Alt + C | Create a new room (start chat) +Alt + F
Ctrl + K | Focus filter * +Alt + Shift + F | Clear filter +Alt + Shift + Up
Alt + Shift + K | Previous room +Alt + Shift + Down
Alt + Shift + J | Next room +Alt + Shift + U | Previous unread +Alt + U | Next unread +Ctrl + Shift + U | Oldest unread +Ctrl + U | Latest unread +Alt + Shift + M | Previous highlight +Alt + M | Next highlight +Ctrl + Shift + M | Oldest highlight +Ctrl + M | Latest highlight +Alt + 1 | Room number 1 in account +Alt + 2 | Room number 2 in account +Alt + 3 | Room number 3 in account +Alt + 4 | Room number 4 in account +Alt + 5 | Room number 5 in account +Alt + 6 | Room number 6 in account +Alt + 7 | Room number 7 in account +Alt + 8 | Room number 8 in account +Alt + 9 | Room number 9 in account +Alt + 0 | Room number 10 in account (no binding) | Jump to specific room by ID ## Chat bindings Key | Function ------ | ------ -Alt + R | Focus room pane -Ctrl + Alt + R | Hide room pane -Alt + I | Invite members -Alt + Escape | Leave current chat -Alt + S | Upload file -Alt + Shift + S | Send file at clipboard path +Alt + R | Focus room pane +Ctrl + Alt + R | Hide room pane +Alt + I | Invite members +Alt + Escape | Leave current chat +Alt + S | Upload file +Alt + Shift + S | Send file at clipboard path ## Message bindings Key | Function ------ | ------ -Ctrl + Up
Ctrl + K | Focus previous message -Ctrl + Down
Ctrl + J | Focus next message -Ctrl + Space | Select focused message -Ctrl + Shift + Space | Select messages until here -Ctrl + D | Unfocus or deselect -Ctrl + S | Display seen tooltips -Ctrl + R
Alt + Del | Remove message -Ctrl + Q | Reply -Ctrl + Shift + D | Debug message -Ctrl + O | Open link/file in message -Ctrl + Shift + O | Open link/file externally -Ctrl + Shift + C | Copy downloaded file path -Ctrl + Shift + L | Clear messages +Ctrl + Up
Ctrl + I | Focus previous message * +Ctrl + Down
Ctrl + J | Focus next message +Ctrl + Space | Select focused message +Ctrl + Shift + Space | Select messages until here +Ctrl + D | Unfocus or deselect +Ctrl + S | Display seen tooltips +Ctrl + Shift + R
Alt + Del | Remove message * +Ctrl + R | Reply * +Ctrl + Shift + D | Debug message +Ctrl + O | Open link/file in message +Ctrl + Shift + O | Open link/file externally +Ctrl + Shift + C | Copy downloaded file path +Ctrl + Shift + L | Clear messages ## Image viewer bindings @@ -121,20 +121,20 @@ Key | Function ------ | ------ X
Q | Close image viewer E | Expand image viewer -F
F11
Alt + Return
Alt + Enter | Fullscreen image viewer -H
Left
Alt + H
Alt + Left | Pan image left -J
Down
Alt + J
Alt + Down | Pan image down -K
Up
Alt + K
Alt + Up | Pan image up -L
Right
Alt + L
Alt + Right | Pan image right -Z
+
Ctrl + + | Zoom in -Shift + Z
-
Ctrl + - | Zoom out -Alt + Z
=
Ctrl + = | Reset zoom +F
F11
Alt + Return
Alt + Enter | Fullscreen image viewer +H
Left
Alt + H
Alt + Left | Pan image left +J
Down
Alt + J
Alt + Down | Pan image down +K
Up
Alt + K
Alt + Up | Pan image up +L
Right
Alt + L
Alt + Right | Pan image right +Z
+
Ctrl + + | Zoom in +Shift + Z
-
Ctrl + - | Zoom out +Alt + Z
=
Ctrl + = | Reset zoom R | Rotate image right -Shift + R | Rotate image left -Alt + R | Reset image rotation +Shift + R | Rotate image left +Alt + R | Reset image rotation S | Speed up gif -Shift + S | Slow down gif -Alt + S | Reset gif speed +Shift + S | Slow down gif +Alt + S | Reset gif speed Space | Pause gif ## Security tab bindings @@ -142,8 +142,10 @@ Key | Function Key | Function ------ | ------ Tab | Navigate next -Shift + Tab | Navigate previous +Shift + Tab | Navigate previous Space | Toggle check Menu | Session context menu -Alt + R
F5 | Refresh session list -Alt + S
Delete | Sign out session +Alt + R
F5 | Refresh session list +Alt + S
Delete | Sign out session + +*Binding different than in Mirage diff --git a/docs/MIRAGEDIFF.md b/docs/MIRAGEDIFF.md new file mode 100644 index 00000000..cd8f3faa --- /dev/null +++ b/docs/MIRAGEDIFF.md @@ -0,0 +1,69 @@ +# Differences between Moment and Mirage + +## Maintenance + +Due to access issues, it is unlikely +Mirage +will continue being maintained. You should check the + +date of the last commit 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: +- Ctrl + Q is `Quit` +- `Reply` is Ctrl + R instead of Ctrl + Q +- `Remove` is Ctrl + Shift + R instead of Ctrl + R +- Ctrl + K is `Focus filter` +- `Focus previous message` is Ctrl + I instead of Ctrl + K + +If you wish to have `Focus previous message` and `Focus next message` +adjacent on Ctrl + U and Ctrl + I, +we recommend swapping Ctrl + U and Ctrl + J +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"] +``` diff --git a/docs/THEMING.md b/docs/THEMING.md index ed19045e..e344c4ee 100644 --- a/docs/THEMING.md +++ b/docs/THEMING.md @@ -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 ``` diff --git a/docs/screenshots/m01-chat.png b/docs/screenshots/m01-chat.png new file mode 100644 index 00000000..c5ad7313 Binary files /dev/null and b/docs/screenshots/m01-chat.png differ diff --git a/docs/screenshots/m02-sign-in.png b/docs/screenshots/m02-sign-in.png new file mode 100644 index 00000000..4a2e2ab4 Binary files /dev/null and b/docs/screenshots/m02-sign-in.png differ diff --git a/docs/screenshots/m03-account-settings.png b/docs/screenshots/m03-account-settings.png new file mode 100644 index 00000000..ef202b6a Binary files /dev/null and b/docs/screenshots/m03-account-settings.png differ diff --git a/docs/screenshots/m04-create-room.png b/docs/screenshots/m04-create-room.png new file mode 100644 index 00000000..2a610105 Binary files /dev/null and b/docs/screenshots/m04-create-room.png differ diff --git a/docs/screenshots/m05-main-pane-small.png b/docs/screenshots/m05-main-pane-small.png new file mode 100644 index 00000000..3d68f2c1 Binary files /dev/null and b/docs/screenshots/m05-main-pane-small.png differ diff --git a/docs/screenshots/m06-chat-small.png b/docs/screenshots/m06-chat-small.png new file mode 100644 index 00000000..79dc86c8 Binary files /dev/null and b/docs/screenshots/m06-chat-small.png differ diff --git a/docs/screenshots/m07-room-pane-small.png b/docs/screenshots/m07-room-pane-small.png new file mode 100644 index 00000000..a0f7ab89 Binary files /dev/null and b/docs/screenshots/m07-room-pane-small.png differ diff --git a/mirage.pro b/moment.pro similarity index 93% rename from mirage.pro rename to moment.pro index ae91489f..38131ac8 100644 --- a/mirage.pro +++ b/moment.pro @@ -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 diff --git a/packaging/appimage/AppRun.sh b/packaging/appimage/AppRun.sh deleted file mode 100755 index 1356add5..00000000 --- a/packaging/appimage/AppRun.sh +++ /dev/null @@ -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" "$@" diff --git a/packaging/appimage/README.md b/packaging/appimage/README.md deleted file mode 100644 index ac629128..00000000 --- a/packaging/appimage/README.md +++ /dev/null @@ -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 /* 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 diff --git a/packaging/appimage/build.sh b/packaging/appimage/build.sh deleted file mode 100755 index 21acbba4..00000000 --- a/packaging/appimage/build.sh +++ /dev/null @@ -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 /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 diff --git a/requirements.txt b/requirements.txt index a276b99e..7c481a43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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] diff --git a/src/backend/__init__.py b/src/backend/__init__.py index fb26c07c..4de73963 100644 --- a/src/backend/__init__.py +++ b/src/backend/__init__.py @@ -1,7 +1,8 @@ # Copyright Mirage authors & contributors +# and Moment contributors # 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" diff --git a/src/backend/backend.py b/src/backend/backend.py index 89a96a24..a540326b 100644 --- a/src/backend/backend.py +++ b/src/backend/backend.py @@ -1,4 +1,5 @@ # Copyright Mirage authors & contributors +# and Moment contributors # 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()): diff --git a/src/backend/media_cache.py b/src/backend/media_cache.py index 3e9a58fc..b6726181 100644 --- a/src/backend/media_cache.py +++ b/src/backend/media_cache.py @@ -1,4 +1,5 @@ # Copyright Mirage authors & contributors +# and Moment contributors # SPDX-License-Identifier: LGPL-3.0-or-later """Matrix media downloading, caching and retrieval.""" @@ -95,7 +96,7 @@ class Media: // _.` ``` - 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): _.` ``` 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) diff --git a/src/backend/user_files.py b/src/backend/user_files.py index 4a43fca1..e855ffb9 100644 --- a/src/backend/user_files.py +++ b/src/backend/user_files.py @@ -1,4 +1,5 @@ # Copyright Mirage authors & contributors +# and Moment contributors # 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) diff --git a/src/config/settings.py b/src/config/settings.py index 16c37d1a..5437ae86 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -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. diff --git a/src/gui/ArgumentParser.qml b/src/gui/ArgumentParser.qml index 8a2e2ae8..67892672 100644 --- a/src/gui/ArgumentParser.qml +++ b/src/gui/ArgumentParser.qml @@ -1,4 +1,5 @@ // Copyright Mirage authors & contributors +// and Moment contributors // 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 ` diff --git a/src/gui/MainPane/TopBar.qml b/src/gui/MainPane/TopBar.qml index df6159a3..04385456 100644 --- a/src/gui/MainPane/TopBar.qml +++ b/src/gui/MainPane/TopBar.qml @@ -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" ) } diff --git a/src/gui/Pages/AccountSettings/Notifications.qml b/src/gui/Pages/AccountSettings/Notifications.qml index db8fb25b..13529e35 100644 --- a/src/gui/Pages/AccountSettings/Notifications.qml +++ b/src/gui/Pages/AccountSettings/Notifications.qml @@ -129,7 +129,7 @@ HListView { Timer { id: autoSaveTimer - interval: 30000 + interval: 3000 onTriggered: root.save() } diff --git a/src/gui/Pages/AddAccount/ServerBrowser.qml b/src/gui/Pages/AddAccount/ServerBrowser.qml index e494730d..b2642914 100644 --- a/src/gui/Pages/AddAccount/ServerBrowser.qml +++ b/src/gui/Pages/AddAccount/ServerBrowser.qml @@ -246,7 +246,7 @@ HBox { Timer { interval: 1000 running: - fetchServersFutureId === "" && + // fetchServersFutureId === "" && ModelStore.get("homeservers").count === 0 repeat: true diff --git a/src/gui/Popups/Pre070SettingsDetectedPopup.qml b/src/gui/Popups/Pre070SettingsDetectedPopup.qml index 6937a1d5..e6ecafd5 100644 --- a/src/gui/Popups/Pre070SettingsDetectedPopup.qml +++ b/src/gui/Popups/Pre070SettingsDetectedPopup.qml @@ -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 { diff --git a/src/gui/Popups/UnexpectedErrorPopup.qml b/src/gui/Popups/UnexpectedErrorPopup.qml index 159fbb22..59183037 100644 --- a/src/gui/Popups/UnexpectedErrorPopup.qml +++ b/src/gui/Popups/UnexpectedErrorPopup.qml @@ -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" ) } diff --git a/src/images/foliage.jpg b/src/images/foliage.jpg new file mode 100644 index 00000000..2642d41e Binary files /dev/null and b/src/images/foliage.jpg differ diff --git a/src/main.cpp b/src/main.cpp index 197a6f4e..82429f4d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ // Copyright Mirage authors & contributors +// and Moment contributors // SPDX-License-Identifier: LGPL-3.0-or-later // This file creates the application, registers custom objects for QML @@ -17,6 +18,7 @@ #include #include #include +#include #include #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); diff --git a/src/themes/Foliage.qpl b/src/themes/Foliage.qpl new file mode 100644 index 00000000..baf16cf0 --- /dev/null +++ b/src/themes/Foliage.qpl @@ -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://, 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: + '\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, + )