diff --git a/.dockerignore b/.dockerignore
index 3cbfcd20d3cf09b99a18fb2bbdd0aca8f5d7ccf8..e66200a3042564cb46798b1416b9594d78f53433 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,7 +1,9 @@
 client-android
 client-gnome
+client-ios
 client-macosx
-client-windows
+client-qt
+client-uwp
 daemon
 lrc
 ring_*
diff --git a/.gitignore b/.gitignore
index d00229a382d70bb769196bd9f83fa0f947313f13..5e135de74b32810f5d5495e19f49772f4c31f3c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,6 @@ repositories
 manual-download
 .docker-image-*
 qemu-static
+
+# QtCreator
+/build-*
diff --git a/.gitmodules b/.gitmodules
index fa9d2c5ca02b24cb104d3ddf266acb2845d56dfe..17349a6d9fccab2e24a318bebbf15be9e6700fce 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,9 +4,6 @@
 [submodule "client-gnome"]
 	path = client-gnome
 	url = https://review.jami.net/ring-client-gnome
-[submodule "client-windows"]
-	path = client-windows
-	url = https://review.jami.net/ring-client-windows
 [submodule "daemon"]
 	path = daemon
 	url = https://review.jami.net/ring-daemon
@@ -25,3 +22,6 @@
 [submodule "client-electron"]
 	path = client-electron
 	url = https://review.jami.net/ring-client-electron
+[submodule "client-qt"]
+	path = client-qt
+	url = https://review.jami.net/jami-client-qt
diff --git a/client-qt b/client-qt
new file mode 160000
index 0000000000000000000000000000000000000000..1f91576a0bf33c9d632595cf433d547d1f1d1e06
--- /dev/null
+++ b/client-qt
@@ -0,0 +1 @@
+Subproject commit 1f91576a0bf33c9d632595cf433d547d1f1d1e06
diff --git a/client-windows b/client-windows
deleted file mode 160000
index 5fac25a5cbfd98a750b8c3e1ef67b36e52d312b3..0000000000000000000000000000000000000000
--- a/client-windows
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 5fac25a5cbfd98a750b8c3e1ef67b36e52d312b3
diff --git a/make-ring.py b/make-ring.py
index 14e1f1c6d43f45b1145b7050833cf9b1f5f0bce2..3d6629f821153ec67d332eeaae8648af4b089c6f 100755
--- a/make-ring.py
+++ b/make-ring.py
@@ -22,9 +22,13 @@ OSX_DISTRIBUTION_NAME = "osx"
 ANDROID_DISTRIBUTION_NAME = "android"
 WIN32_DISTRIBUTION_NAME = "win32"
 
-# vs help
+# Qt 5.15 is currently only available using the maintenance tool.
+QT5_VERSION = "5.15"
+DEFAULT_QT_5_15_PATH = "~/Qt/5.15.0/gcc_64"
+
+# vs vars
 win_sdk_default = '10.0.16299.0'
-win_toolset_default = 'v141'
+win_toolset_default = 'v142'
 
 APT_BASED_DISTROS = [
     'debian',
@@ -110,7 +114,7 @@ DNF_DEPENDENCIES = [
     'gtk3-devel', 'clutter-devel', 'clutter-gtk-devel',
     'libnotify-devel', 'libappindicator-gtk3-devel', 'patch', 'libva-devel', 'openssl-devel',
     'webkitgtk4-devel', 'NetworkManager-libnm-devel', 'libvdpau-devel', 'msgpack-devel', 'libcanberra-devel',
-    'sqlite-devel', 'openssl-static'
+    'sqlite-devel', 'openssl-static', 'pandoc'
 ]
 
 APT_DEPENDENCIES = [
@@ -124,7 +128,8 @@ APT_DEPENDENCIES = [
     'libspeex-dev', 'libspeexdsp-dev', 'libswscale-dev', 'libtool',
     'libudev-dev', 'libyaml-cpp-dev', 'qtbase5-dev', 'libqt5sql5-sqlite', 'sip-tester', 'swig',
     'uuid-dev', 'yasm', 'libqrencode-dev', 'libjsoncpp-dev', 'libappindicator3-dev',
-    'libva-dev', 'libwebkit2gtk-4.0-dev', 'libnm-dev', 'libvdpau-dev', 'libmsgpack-dev', 'libcanberra-gtk3-dev'
+    'libva-dev', 'libwebkit2gtk-4.0-dev', 'libnm-dev', 'libvdpau-dev', 'libmsgpack-dev', 'libcanberra-gtk3-dev',
+    'pandoc'
 ]
 
 PACMAN_DEPENDENCIES = [
@@ -182,8 +187,22 @@ def run_powersell_cmd(cmd):
     return
 
 
+def write_qt_conf(path):
+    # Add a configuration that can be supplied to qmake
+    # e.g. `qmake -qt=5.15 [mode] [options] [files]`
+    if path is '':
+        return
+    with open('/usr/share/qtchooser/' + QT5_VERSION + '.conf', 'w+') as fd:
+        fd.write(path.rstrip('/') + '/bin\n')
+        fd.write(path.rstrip('/') + '/lib\n')
+    return
+
+
 def run_dependencies(args):
-    if(args.distribution == WIN32_DISTRIBUTION_NAME):
+    if args.qt is not None:
+        write_qt_conf(args.qt)
+
+    if args.distribution == WIN32_DISTRIBUTION_NAME:
         run_powersell_cmd(
             'Set-ExecutionPolicy Unrestricted; .\\scripts\\build-package-windows.ps1')
 
@@ -258,7 +277,8 @@ def run_init():
                 module_names.append(line[line.find('"')+1:line.rfind('"')])
 
     subprocess.run(["git", "submodule", "update", "--init"], check=True)
-    subprocess.run(["git", "submodule", "foreach", "git checkout master && git pull"], check=True)
+    subprocess.run(["git", "submodule", "foreach",
+                    "git checkout master && git pull"], check=True)
     for name in module_names:
         copy_file("./scripts/commit-msg", ".git/modules/"+name+"/hooks")
 
@@ -283,7 +303,8 @@ def run_install(args):
         return subprocess.run(["./compile.sh"], cwd="./client-android", check=True)
     elif args.distribution == WIN32_DISTRIBUTION_NAME:
         return subprocess.run([
-            sys.executable, os.path.join(os.getcwd(), "scripts/build-windows.py"),
+            sys.executable, os.path.join(
+                os.getcwd(), "scripts/build-windows.py"),
             "--toolset", args.toolset,
             "--sdk", args.sdk,
             "--qtver", args.qtver
@@ -311,13 +332,21 @@ def run_install(args):
                               universal_newlines=True)
 
         environ['CMAKE_PREFIX_PATH'] = proc.stdout.rstrip("\n")
-        environ['CONFIGURE_FLAGS']   = '--without-dbus'
+        environ['CONFIGURE_FLAGS'] = '--without-dbus'
         install_args += ("-c", "client-macosx")
     else:
         if args.distribution in ZYPPER_BASED_DISTROS:
             # fix jsoncpp pkg-config bug, remove when jsoncpp package bumped
             environ['JSONCPP_LIBS'] = "-ljsoncpp"
-        install_args += ("-c", "client-gnome")
+        if args.qt is None:
+            install_args += ("-c", "client-gnome")
+        else:
+            install_args += ("-c", "client-qt")
+            install_args += ("-q", QT5_VERSION)
+            if args.qt is '':
+                install_args += ("-Q", DEFAULT_QT_5_15_PATH)
+            else:
+                install_args += ("-Q", args.qt)
 
     return subprocess.run(["./scripts/install.sh"] + install_args, env=environ, check=True)
 
@@ -352,11 +381,17 @@ def run_run(args):
         with open('daemon.pid', 'w') as f:
             f.write(str(dring_process.pid)+'\n')
 
-        client_log = open("jami-gnome.log", 'a')
+        client_suffix = ""
+        if args.qt is not None:
+            client_suffix += "qt"
+        else:
+            client_suffix += "gnome"
+        client_log = open("jami-" + client_suffix + ".log", 'a')
         client_log.write('=== Starting client (%s) ===' %
                          time.strftime("%d/%m/%Y %H:%M:%S"))
         client_process = subprocess.Popen(
-            ["./install/client-gnome/bin/jami-gnome", "-d"],
+            ["./install/client-" + client_suffix +
+                "/bin/jami-" + client_suffix, "-d"],
             stdout=client_log,
             stderr=client_log,
             env=run_env
@@ -416,7 +451,7 @@ def execute_script(script, settings=None, fail=True):
 def validate_args(parsed_args):
     """Validate the args values, exit if error is found"""
 
-    # Check arg values
+    # Filter unsupported distributions.
     supported_distros = [
         ANDROID_DISTRIBUTION_NAME, OSX_DISTRIBUTION_NAME, IOS_DISTRIBUTION_NAME,
         WIN32_DISTRIBUTION_NAME
@@ -429,6 +464,24 @@ def validate_args(parsed_args):
         ), file=sys.stderr)
         sys.exit(1)
 
+    # The Qt client support will be added incrementally.
+    if parsed_args.qt is not None:
+        supported_qt_distros = [
+            WIN32_DISTRIBUTION_NAME, APT_BASED_DISTROS, DNF_BASED_DISTROS
+        ]
+        if parsed_args.distribution not in supported_distros:
+            print('Distribution \'{0}\' not supported when building the Qt client.'
+                  '\nChoose one of: {1}'.format(
+                      parsed_args.distribution, ', '.join(supported_qt_distros)
+                  ), file=sys.stderr)
+            sys.exit(1)
+
+    # The windows client can only be built on a Windows 10 host.
+    if parsed_args.distribution == WIN32_DISTRIBUTION_NAME:
+        if platform.release() != '10':
+            print('Windows version must be built on Windows 10')
+            sys.exit(1)
+
 
 def parse_args():
     ap = argparse.ArgumentParser(description="Ring build tool")
@@ -459,24 +512,26 @@ def parse_args():
     ap.add_argument('--global-install', default=False, action='store_true')
     ap.add_argument('--debug', default=False, action='store_true')
     ap.add_argument('--background', default=False, action='store_true')
-    ap.add_argument('--no-priv-install', dest='priv_install', default=True, action='store_false')
-
-    if choose_distribution() == WIN32_DISTRIBUTION_NAME:
-        ap.add_argument('--toolset', default=win_toolset_default, type=str, help='Windows use only, specify Visual Studio toolset version')
-        ap.add_argument('--sdk', default=win_sdk_default, type=str, help='Windows use only, specify Windows SDK version')
-        ap.add_argument('--qtver', default='5.9.4', help='Sets the Qt version to build with')
+    ap.add_argument('--no-priv-install', dest='priv_install',
+                    default=True, action='store_false')
+    ap.add_argument('--qt', nargs='?', const='', type=str,
+                    help='Build the Qt client with the Qt 5.15 path supplied')
+
+    dist = choose_distribution()
+    if dist == WIN32_DISTRIBUTION_NAME:
+        ap.add_argument('--toolset', default=win_toolset_default, type=str,
+                        help='Windows use only, specify Visual Studio toolset version')
+        ap.add_argument('--sdk', default=win_sdk_default, type=str,
+                        help='Windows use only, specify Windows SDK version')
+        ap.add_argument('--qtver', default='5.15.0',
+                        help='Sets the Qt version to build with')
 
     parsed_args = ap.parse_args()
 
     if (parsed_args.distribution is not None):
         parsed_args.distribution = parsed_args.distribution.lower()
     else:
-        parsed_args.distribution = choose_distribution()
-
-    if parsed_args.distribution == WIN32_DISTRIBUTION_NAME:
-        if platform.release() != '10':
-            print('Windows version must be built on Windows 10')
-            sys.exit(1)
+        parsed_args.distribution = dist
 
     validate_args(parsed_args)
 
diff --git a/packaging/rules/debian-one-click-install/copyright b/packaging/rules/debian-one-click-install/copyright
index d1f27599c012280c06dca121df2daf3f21da65b6..73da38fc6d7fd99caf7a84c6f7658d9d9b2f79f0 100644
--- a/packaging/rules/debian-one-click-install/copyright
+++ b/packaging/rules/debian-one-click-install/copyright
@@ -4,7 +4,7 @@ Upstream-Contact: Alexandre Viau <alexandre.viau@savoirfairelinux.net>
 Source: https://dl.jami.net/release/tarballs/
 Files-Excluded: client-electron/*
                 client-uwp/*
-                client-windows/*
+                client-qt/*
                 client-android/*
                 client-macosx/*
                 client-ios/*
diff --git a/packaging/rules/debian/copyright b/packaging/rules/debian/copyright
index d1f27599c012280c06dca121df2daf3f21da65b6..73da38fc6d7fd99caf7a84c6f7658d9d9b2f79f0 100644
--- a/packaging/rules/debian/copyright
+++ b/packaging/rules/debian/copyright
@@ -4,7 +4,7 @@ Upstream-Contact: Alexandre Viau <alexandre.viau@savoirfairelinux.net>
 Source: https://dl.jami.net/release/tarballs/
 Files-Excluded: client-electron/*
                 client-uwp/*
-                client-windows/*
+                client-qt/*
                 client-android/*
                 client-macosx/*
                 client-ios/*
diff --git a/scripts/build-windows.py b/scripts/build-windows.py
index 1980f6fb5b47a8acf0591eb798edd7eb00a1fc36..1eb2265472198d2aca208c4f365a2fdcdf0aba95 100644
--- a/scripts/build-windows.py
+++ b/scripts/build-windows.py
@@ -15,7 +15,8 @@ def execute_cmd(cmd, with_shell=False):
 def build_daemon(parsed_args):
     make_cmd = os.path.dirname(this_dir) + '\\daemon\\compat\\msvc\\winmake.py'
     os.chdir(os.path.dirname(this_dir) + '\\daemon\\compat\\msvc')
-    status_code = execute_cmd('python ' + make_cmd + ' -iv -t ' + parsed_args.toolset + ' -s ' + parsed_args.sdk + ' -b daemon')
+    status_code = execute_cmd('python ' + make_cmd + ' -iv -t ' +
+                              parsed_args.toolset + ' -s ' + parsed_args.sdk + ' -b daemon')
     os.chdir(os.path.dirname(this_dir))
     return status_code
 
@@ -26,17 +27,20 @@ def build_lrc(parsed_args):
 
 
 def build_client(parsed_args):
-    os.chdir('./client-windows')
+    os.chdir('./client-qt')
     ret = 0
-    ret &= not execute_cmd('pandoc -f markdown -t html5 -o changelog.html changelog.md', True)
+    ret &= not execute_cmd(
+        'pandoc -f markdown -t html5 -o changelog.html changelog.md', True)
     ret &= not execute_cmd('python make-client.py -d')
-    ret &= not execute_cmd('python make-client.py -b ' + '-t ' + parsed_args.toolset + ' -s ' + parsed_args.sdk + ' -q ' + parsed_args.qtver)
+    ret &= not execute_cmd('python make-client.py -b ' + '-t ' +
+                           parsed_args.toolset + ' -s ' + parsed_args.sdk + ' -q ' + parsed_args.qtver)
 
     if not os.path.exists('./x64/Release/qt.conf'):
         ret &= not execute_cmd(
             'powershell -ExecutionPolicy Unrestricted -File copy-runtime-files.ps1' + ' "Release" ' + '"' + parsed_args.qtver + '"', True)
     return ret
 
+
 def parse_args():
     ap = argparse.ArgumentParser(description="Qt Client build tool")
 
@@ -44,14 +48,14 @@ def parse_args():
                     help='Windows use only, specify Visual Studio toolset version')
     ap.add_argument('--sdk', default='', type=str,
                     help='Windows use only, specify Windows SDK version')
-    ap.add_argument('--qtver', default='5.9.4',
+    ap.add_argument('--qtver', default='5.15.0',
                     help='Sets the Qt version to build with')
 
     parsed_args = ap.parse_args()
 
-
     return parsed_args
 
+
 def main():
 
     parsed_args = parse_args()
diff --git a/scripts/install.sh b/scripts/install.sh
index ba91024895e154f1d9b8dc303d538392c106f2de..5ddc324716e12eb84e84d50cc4178431739bc529 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -16,9 +16,11 @@ set -ex
 global=false
 static=''
 client=''
+qt5ver=''
+qt5path=''
 proc='1'
 priv_install=true
-while getopts gsc:P:p:u OPT; do
+while getopts gsc:q:Q:P:p:u OPT; do
   case "$OPT" in
     g)
       global='true'
@@ -29,6 +31,12 @@ while getopts gsc:P:p:u OPT; do
     c)
       client="${OPTARG}"
     ;;
+    q)
+      qt5ver="${OPTARG}"
+    ;;
+    Q)
+      qt5path="${OPTARG}"
+    ;;
     P)
       prefix="${OPTARG}"
     ;;
@@ -64,6 +72,7 @@ else
     BUILDDIR="build-local"
 fi
 
+# dring
 cd "${TOP}/daemon"
 DAEMON="$(pwd)"
 cd contrib
@@ -95,38 +104,59 @@ fi
 make -j"${proc}"
 make_install "${global}" "${priv_install}"
 
+# libringclient
 cd "${TOP}/lrc"
 mkdir -p "${BUILDDIR}"
 cd "${BUILDDIR}"
 if [ "${global}" = "true" ]; then
   if [ "${prefix+set}" ]; then
-    cmake .. -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX="${prefix}" $static
+    cmake .. -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" \
+             -DCMAKE_BUILD_TYPE=Debug \
+             -DCMAKE_INSTALL_PREFIX="${prefix}" $static \
+             -DQT_MIN_VER="${qt5ver}" \
+             -DQT5_PATH="${qt5path}"
   else
-    cmake .. -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" -DCMAKE_BUILD_TYPE=Debug $static
+    cmake .. -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" \
+             -DCMAKE_BUILD_TYPE=Debug $static \
+             -DQT_MIN_VER="${qt5ver}" \
+             -DQT5_PATH="${qt5path}"
   fi
 else
   cmake ..  -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" \
             -DCMAKE_BUILD_TYPE=Debug \
             -DCMAKE_INSTALL_PREFIX="${INSTALL}/lrc" \
-            -DRING_BUILD_DIR="${DAEMON}/src" $static
+            -DRING_BUILD_DIR="${DAEMON}/src" $static \
+            -DQT_MIN_VER="${qt5ver}" \
+            -DQT5_PATH="${qt5path}"
 fi
 make -j"${proc}"
 make_install "${global}"  "${priv_install}"
 
+# client
 cd "${TOP}/${client}"
 mkdir -p "${BUILDDIR}"
 cd "${BUILDDIR}"
-if [ "${global}" = "true" ]; then
-  if [ "${prefix+set}" ]; then
-    cmake .. -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" -DCMAKE_INSTALL_PREFIX="${prefix}" $static
-  else
-    cmake .. -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" $static
-  fi
+if [ "${client}" = "client-qt" ]; then
+    echo building client-qt using Qt ${qt5ver}
+    pandoc -f markdown -t html5 -o ../changelog.html ../changelog.md
+    if ! command -v qmake &> /dev/null; then
+      eval ${qt5path}/bin/qmake PREFIX="${INSTALL}/${client}" ..
+    else
+      qmake -qt=${qt5ver} PREFIX="${INSTALL}/${client}" ..
+    fi
 else
-  cmake ..  -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" \
-            -DCMAKE_INSTALL_PREFIX="${INSTALL}/${client}" \
-            -DRINGTONE_DIR="${INSTALL}/daemon/share/ring/ringtones" \
-            -DLibRingClient_DIR="${INSTALL}/lrc/lib/cmake/LibRingClient" $static
+    if [ "${global}" = "true" ]; then
+      if [ "${prefix+set}" ]; then
+        cmake .. -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" -DCMAKE_INSTALL_PREFIX="${prefix}" $static
+      else
+        cmake .. -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" $static
+      fi
+    else
+      cmake ..  -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" \
+                -DCMAKE_INSTALL_PREFIX="${INSTALL}/${client}" \
+                -DRINGTONE_DIR="${INSTALL}/daemon/share/ring/ringtones" \
+                -DLibRingClient_DIR="${INSTALL}/lrc/lib/cmake/LibRingClient" $static
+    fi
 fi
 make -j"${proc}"
 make_install "${global}" "${priv_install}"
diff --git a/scripts/update-submodules.sh b/scripts/update-submodules.sh
index 0c437623b045df0e3696bce69d4686a54f1a8c73..15befcc6cbcc7a384c5a19eed9128bff80fcc941 100755
--- a/scripts/update-submodules.sh
+++ b/scripts/update-submodules.sh
@@ -1,5 +1,12 @@
 #!/usr/bin/env bash
 
 git submodule foreach "git pull origin master"
-git add client-android client-gnome client-macosx client-windows daemon lrc
+git add client-android \
+        client-gnome \
+        client-ios \
+        client-macosx \
+        client-qt \
+        client-uwp \
+        daemon \
+        lrc
 git commit