diff --git a/.github/workflows/build-win64.yml b/.github/workflows/build-win64.yml
new file mode 100644
index 00000000..3ca47c88
--- /dev/null
+++ b/.github/workflows/build-win64.yml
@@ -0,0 +1,26 @@
+name: Build for Windows
+on: [pull_request, push]
+
+jobs:
+ build:
+ runs-on: windows-latest
+ steps:
+ - uses: msys2/setup-msys2@v2
+ with:
+ msystem: MINGW64
+ update: true
+ install: git
+ - run: git config --global core.autocrlf input
+ - uses: actions/checkout@v4
+ - name: Build Dino
+ run: |
+ msys2 -c './build-win64.sh --prepare'
+ msys2 -c './build-win64.sh'
+ - name: Build Dino Installer
+ run: |
+ msys2 -c './build-win64.sh --build-installer'
+ - name: Upload Dino Installer
+ uses: actions/upload-artifact@v4
+ with:
+ name: dino-installer
+ path: windows-installer/dino-installer.exe
diff --git a/.gitignore b/.gitignore
index 47bfd683..6d362804 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,6 @@ Makefile
.idea
.sqlite3
gschemas.compiled
+windows-installer/win64-dist/
+*.exe
+*.dll
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1365b6d2..2b24e424 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,6 +12,9 @@ endif ()
# Prepare Plugins
set(DEFAULT_PLUGINS omemo;openpgp;http-files;ice;rtp)
+if (WIN32)
+ set(DEFAULT_PLUGINS ${DEFAULT_PLUGINS};win32-fonts;windows-notification)
+endif (WIN32)
foreach (plugin ${DEFAULT_PLUGINS})
if ("$CACHE{DINO_PLUGIN_ENABLED_${plugin}}" STREQUAL "")
if (NOT DEFINED DINO_PLUGIN_ENABLED_${plugin}})
@@ -176,6 +179,11 @@ if (NOT NO_DEBUG)
set(CMAKE_VALA_FLAGS "${CMAKE_VALA_FLAGS} -g")
endif (NOT NO_DEBUG)
+if (WIN32)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_POSIX_C_SOURCE=1")
+ set(CMAKE_VALA_FLAGS "${CMAKE_VALA_FLAGS} --define=_WIN32")
+endif(WIN32)
+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
diff --git a/README-WIN64.md b/README-WIN64.md
new file mode 100644
index 00000000..15666e05
--- /dev/null
+++ b/README-WIN64.md
@@ -0,0 +1,56 @@
+![Dino (WIN64)](https://dino.im/img/readme_header.svg)
+=======
+
+![screenshots](https://dino.im/img/screenshot-main.png)
+
+Build on Windows (x86_64)
+------------
+- Install and configure the [MSYS2](https://www.msys2.org/) package;
+- Go to `MINGW64` environment;
+- Clone project:
+ ```sh
+ git clone https://github.com/mxlgv/dino && cd dino
+ ```
+- Run the script to install dependencies:
+ ```sh
+ ./build-win64.sh --prepare
+ ```
+- Start the build (the builded distribution is available in the `windows-installer/dist-win64` folder):
+ ```sh
+ ./build-win64.sh
+ ```
+Note: the build script has some other options, their description can be found using the `--help`.
+
+Build Windows Installer (NSIS)
+------------
+Before this, you must build the project according to the instructions above. It's worth making sure that `windows-installer/dist-win64` is not empty.
+Now you should run:
+```sh
+./build-win64.sh --build-installer
+```
+
+The builded installer will be available in the directory `windows-installer/dino-installer.exe`.
+
+Resources
+---------
+- Check out the [Dino website](https://dino.im).
+- Join our XMPP channel at `chat@dino.im`.
+- The [wiki](https://github.com/dino/dino/wiki) provides additional information.
+
+License
+-------
+ Dino - Modern Jabber/XMPP Client using GTK+/Vala
+ Copyright (C) 2016-2023 Dino contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
diff --git a/build-win64.sh b/build-win64.sh
new file mode 100644
index 00000000..4143a154
--- /dev/null
+++ b/build-win64.sh
@@ -0,0 +1,182 @@
+#!/bin/bash
+set -e
+
+DIST_DIR="$PWD/windows-installer/win64-dist"
+JOBS=$NUMBER_OF_PROCESSORS
+
+msg()
+{
+ echo -e "\e[32m$1\e[0m"
+}
+
+fatal()
+{
+ echo -e "\e[31m$1\e[0m"
+ exit 1
+}
+
+download_yolort()
+{
+ file_name=cppwinrt-2.0.210122.3+windows-10.0.19041+yolort-835cd4e.zip
+ yolort_dir="$PWD/plugins/windows-notification/yolort"
+
+ rm -rf "$yolort_dir"
+ mkdir "$yolort_dir"
+ curl -L -o "$file_name" "https://github.com/LAGonauta/YoloRT/releases/download/v1.0.0/$file_name"
+ echo "675a6d943c97b4acdbfaa473f68d3241d1798b31a67b5529c8d29fc0176a1707 $file_name" | sha256sum --check --status
+ unzip -o "$file_name" -d "$yolort_dir"
+ rm -f "$file_name"
+}
+
+prepare()
+{
+ msg "Installing MINGW64 build dependencies"
+
+ pacman -S --needed --noconfirm \
+ mingw64/mingw-w64-x86_64-gcc \
+ mingw64/mingw-w64-x86_64-cmake \
+ mingw64/mingw-w64-x86_64-ninja \
+ mingw64/mingw-w64-x86_64-gtk4 \
+ mingw64/mingw-w64-x86_64-libadwaita \
+ mingw64/mingw-w64-x86_64-sqlite3 \
+ mingw64/mingw-w64-x86_64-openssl \
+ mingw64/mingw-w64-x86_64-libgcrypt \
+ mingw64/mingw-w64-x86_64-libgee \
+ mingw64/mingw-w64-x86_64-vala \
+ mingw64/mingw-w64-x86_64-gsettings-desktop-schemas \
+ mingw64/mingw-w64-x86_64-qrencode \
+ mingw64/mingw-w64-x86_64-ntldd-git \
+ mingw64/mingw-w64-x86_64-gpgme \
+ mingw64/mingw-w64-x86_64-fontconfig \
+ mingw64/mingw-w64-x86_64-iso-codes \
+ mingw64/mingw-w64-x86_64-gstreamer \
+ mingw64/mingw-w64-x86_64-gst-plugins-bad \
+ mingw64/mingw-w64-x86_64-gst-plugins-good \
+ mingw64/mingw-w64-x86_64-gst-plugins-base \
+ mingw64/mingw-w64-x86_64-gst-plugins-ugly \
+ mingw64/mingw-w64-x86_64-nsis \
+ mingw64/mingw-w64-x86_64-libsignal-protocol-c \
+ mingw64/mingw-w64-x86_64-icu \
+ mingw64/mingw-w64-x86_64-webrtc-audio-processing \
+ git \
+ make \
+ unzip \
+ curl
+
+ msg "Successfully installed!"
+
+ msg "Download YoloRT headers"
+ download_yolort
+ msg "Successfully downloaded!"
+
+}
+
+configure()
+{
+ msg "Running configuration for Windows"
+ ./configure --program-prefix="$DIST_DIR" --no-debug --release --disable-fast-vapi --with-libsoup3
+ msg "Configured!"
+}
+
+build()
+{
+ msg "Started building on $JOBS threads"
+ make -j"$JOBS"
+ msg "Successfully builded!"
+}
+
+dist_install()
+{
+ msg "Installing Dino in '$DIST_DIR'!"
+ make install
+
+ msg "Copying MINGW64 dependencies"
+ cp /mingw64/bin/gdbus.exe "$DIST_DIR/bin"
+ cp /mingw64/bin/gspawn-win64-helper.exe "$DIST_DIR/bin"
+
+ cp /mingw64/bin/libcrypto-*-x64.dll "$DIST_DIR/bin/"
+ cp -r /mingw64/lib/gstreamer-1.0 "$DIST_DIR/lib"
+ mkdir -p "$DIST_DIR/lib/gdk-pixbuf-2.0/" && cp -r /mingw64/lib/gdk-pixbuf-2.0 "$DIST_DIR/lib/"
+ mkdir -p "$DIST_DIR/lib/gio/" && cp -r /mingw64/lib/gio "$DIST_DIR/lib/"
+
+ list=`find "$DIST_DIR" -type f \( -name "*.exe" -o -name "*.dll" \) -exec \
+ ntldd -R {} + | \
+ grep "mingw64" | \
+ cut -f1 -d "=" | sort | uniq`
+ for a in $list; do
+ cp -fv "/mingw64/bin/$a" "$DIST_DIR/bin/"
+ done
+
+ msg "Removing debug information from all EXE and DLL files"
+ find "$DIST_DIR" -iname "*.exe" -exec strip -s {} +
+ find "$DIST_DIR" -iname "*.dll" -exec strip -s {} +
+
+ find "$DIST_DIR" -iname "*.a" -exec rm {} +
+
+ msg "Removing redudant header files"
+ rm -rf "$DIST_DIR/include"
+
+ msg "Copy LICENSE"
+ cp -f "$PWD/LICENSE" "$DIST_DIR/LICENSE"
+
+ msg "Copy icons, themes, locales and fonts"
+ cp -f "$PWD/main/dino.ico" "$DIST_DIR/dino.ico"
+ cp -rf "/mingw64/share/xml" "$DIST_DIR/share"
+ mkdir -p "$DIST_DIR/etc/fonts" && cp -r /mingw64/etc/fonts "$DIST_DIR/etc/"
+ mkdir -p "$DIST_DIR/share/icons" && cp -r /mingw64/share/icons "$DIST_DIR/share/"
+ mkdir -p "$DIST_DIR/share/glib-2.0/schemas" && cp -rf /mingw64/share/glib-2.0/schemas "$DIST_DIR/share/glib-2.0/"
+
+ msg "Successfully installed!"
+}
+
+build_installer()
+{
+ msg "Building an installer for Windows using NSIS"
+ cd windows-installer
+ makensis dino.nsi
+ msg "Installer successfully builded!"
+ cd ..
+}
+
+clean()
+{
+ rm -rf build "$DIST_DIR"
+ msg "Build artifacts removed successfull!"
+}
+
+help()
+{
+cat << EOF
+usage: $0 [OPTION]
+ --prepare install build dependencies
+ --configure configure the project
+ --build build the project
+ --dist-install install the builded project
+ --build-installer build installer (using NSIS)
+ --clean remove build artifacts
+ --help show this help
+
+Running without parameters is equivalent to running:
+'--configure', '--build' and '--dist-install'
+EOF
+}
+
+if [[ "$(uname)" != "MINGW64_NT"* ]]; then
+ fatal "This is not a MINGW64 environment!"
+fi
+
+case $1 in
+ "--prepare" ) prepare ;;
+ "--configure" ) configure ;;
+ "--build" ) build ;;
+ "--dist-install" ) dist_install ;;
+ "--build-installer") build_installer ;;
+ "--clean" ) clean ;;
+ "--help" ) help ;;
+ "" )
+ configure
+ build
+ dist_install
+ ;;
+ *) fatal "Unknown argument!"
+esac
diff --git a/cmake/PkgConfigWithFallback.cmake b/cmake/PkgConfigWithFallback.cmake
index 9124bb35..f2bfdf66 100644
--- a/cmake/PkgConfigWithFallback.cmake
+++ b/cmake/PkgConfigWithFallback.cmake
@@ -13,11 +13,16 @@ function(find_pkg_config_with_fallback name)
# Found via pkg-config, using its result values
set(${name}_FOUND ${${name}_PKG_CONFIG_FOUND})
+ if(MINGW)
+ set(MINGWLIBPATH ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES})
+ endif(MINGW)
+
# Try to find real file name of libraries
foreach(lib ${${name}_PKG_CONFIG_LIBRARIES})
- find_library(${name}_${lib}_LIBRARY ${lib} HINTS ${${name}_PKG_CONFIG_LIBRARY_DIRS})
+ find_library(${name}_${lib}_LIBRARY ${lib} HINTS ${${name}_PKG_CONFIG_LIBRARY_DIRS} ${MINGWLIBPATH})
mark_as_advanced(${name}_${lib}_LIBRARY)
if(NOT ${name}_${lib}_LIBRARY)
+ message(${name} ": " ${lib} " library not found")
unset(${name}_FOUND)
endif(NOT ${name}_${lib}_LIBRARY)
endforeach(lib)
diff --git a/cmake/PkgConfigWithFallbackOnConfigScript.cmake b/cmake/PkgConfigWithFallbackOnConfigScript.cmake
index 4bc94a33..ca18d5bf 100644
--- a/cmake/PkgConfigWithFallbackOnConfigScript.cmake
+++ b/cmake/PkgConfigWithFallbackOnConfigScript.cmake
@@ -13,11 +13,16 @@ function(find_pkg_config_with_fallback_on_config_script name)
# Found via pkg-config, using it's result values
set(${name}_FOUND ${${name}_PKG_CONFIG_FOUND})
+ if(MINGW)
+ set(MINGWLIBPATH ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES})
+ endif(MINGW)
+
# Try to find real file name of libraries
foreach(lib ${${name}_PKG_CONFIG_LIBRARIES})
- find_library(${name}_${lib}_LIBRARY ${lib} HINTS ${${name}_PKG_CONFIG_LIBRARY_DIRS})
+ find_library(${name}_${lib}_LIBRARY ${lib} HINTS ${${name}_PKG_CONFIG_LIBRARY_DIRS} ${MINGWLIBPATH})
mark_as_advanced(${name}_${lib}_LIBRARY)
if(NOT ${name}_${lib}_LIBRARY)
+ message(${name} ": " ${lib} " library not found")
unset(${name}_FOUND)
endif(NOT ${name}_${lib}_LIBRARY)
endforeach(lib)
diff --git a/libdino/src/service/avatar_manager.vala b/libdino/src/service/avatar_manager.vala
index 3bd38e72..015e6822 100644
--- a/libdino/src/service/avatar_manager.vala
+++ b/libdino/src/service/avatar_manager.vala
@@ -304,6 +304,24 @@ public class AvatarManager : StreamInteractionModule, Object {
return null;
}
}
+
+ public string? get_avatar_filepath(Account account, Jid jid_) {
+ Jid jid = jid_;
+ if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid_, account)) {
+ jid = jid_.bare_jid;
+ }
+
+ string? hash = null;
+ if (user_avatars.has_key(jid)) {
+ hash = user_avatars[jid];
+ } else if (vcard_avatars.has_key(jid)) {
+ hash = vcard_avatars[jid];
+ }
+
+ if (hash == null) return null;
+
+ return Path.build_filename(folder, hash);
+ }
}
}
diff --git a/libdino/src/service/file_manager.vala b/libdino/src/service/file_manager.vala
index 984fe5fd..4073fe40 100644
--- a/libdino/src/service/file_manager.vala
+++ b/libdino/src/service/file_manager.vala
@@ -63,7 +63,7 @@ public class FileManager : StreamInteractionModule, Object {
try {
FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE);
file_transfer.file_name = file_info.get_display_name();
- file_transfer.mime_type = file_info.get_content_type();
+ file_transfer.mime_type = Util.get_content_type(file_info);
file_transfer.size = (int)file_info.get_size();
file_transfer.input_stream = yield file.read_async();
@@ -259,9 +259,16 @@ public class FileManager : StreamInteractionModule, Object {
file_transfer.input_stream = yield file.read_async();
FileInfo file_info = file_transfer.get_file().query_info("*", FileQueryInfoFlags.NONE);
- file_transfer.mime_type = file_info.get_content_type();
+ file_transfer.mime_type = Util.get_content_type(file_info);
file_transfer.state = FileTransfer.State.COMPLETE;
+
+#if _WIN32 // Add Zone.Identifier so Windows knows this file was downloaded from the internet
+ var file_alternate_stream = File.new_for_path(Path.build_filename(get_storage_dir(), filename + ":Zone.Identifier"));
+ var os_alternate_stream = file_alternate_stream.create(FileCreateFlags.REPLACE_DESTINATION);
+ os_alternate_stream.write("[ZoneTransfer]\r\nZoneId=3".data);
+#endif
+
} catch (Error e) {
warning("Error downloading file: %s", e.message);
file_transfer.state = FileTransfer.State.FAILED;
diff --git a/libdino/src/service/util.vala b/libdino/src/service/util.vala
index 1d04ffcf..496994c8 100644
--- a/libdino/src/service/util.vala
+++ b/libdino/src/service/util.vala
@@ -4,6 +4,25 @@ using Qlite;
namespace Dino {
public class Util {
+ #if _WIN32
+ [CCode (cname = "ShellExecuteA", cheader_filename = "windows.h")]
+ private static extern int ShellExecuteA(int* hwnd, string operation, string file, string parameters, string directory, int showCmd);
+
+ [CCode (cname = "CoInitialize", cheader_filename = "windows.h")]
+ private static extern int CoInitialize(void* reserved);
+
+ [CCode (cname = "CoUninitialize", cheader_filename = "windows.h")]
+ private static extern void CoUninitialize();
+
+ private static int ShellExecute(string operation, string file) {
+ CoInitialize(null);
+ var result = ShellExecuteA(null, operation, file, null, null, 1);
+ CoUninitialize();
+
+ return result;
+ }
+ #endif
+
public static Message.Type get_message_type_for_conversation(Conversation conversation) {
switch (conversation.type_) {
case Conversation.Type.CHAT:
@@ -29,6 +48,33 @@ public class Util {
assert_not_reached();
}
}
-}
+ public static void launch_default_for_uri(string file_uri)
+ {
+#if _WIN32
+ ShellExecute("open", file_uri);
+#else
+ AppInfo.launch_default_for_uri(file_uri, null);
+#endif
+ }
+
+ public static string get_content_type(FileInfo fileInfo)
+ {
+#if _WIN32
+ string fileName = fileInfo.get_name();
+ int fileNameLength = fileName.length;
+ int extIndex = fileName.last_index_of(".");
+ if (extIndex < fileNameLength)
+ {
+ string extension = fileName.substring(extIndex, fileNameLength - extIndex);
+ string mime_type = ContentType.get_mime_type(extension);
+ if (mime_type != null && mime_type.length != 0)
+ {
+ return mime_type;
+ }
+ }
+#endif
+ return fileInfo.get_content_type();
+ }
+}
}
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index ea4de99b..8e4e811c 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -256,7 +256,13 @@ OPTIONS
)
add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DG_LOG_DOMAIN="dino")
-add_executable(dino ${MAIN_VALA_C} ${MAIN_GRESOURCES_TARGET})
+if(WIN32)
+ add_link_options("-Wl,--export-all-symbols")
+ set(CMAKE_RC_COMPILE_OBJECT " --use-temp-file -O coff -i