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 -o ") + add_executable(dino ${MAIN_VALA_C} ${MAIN_GRESOURCES_TARGET} dino-info.rc) +else(WIN32) + add_executable(dino ${MAIN_VALA_C} ${MAIN_GRESOURCES_TARGET}) +endif(WIN32) add_dependencies(dino ${GETTEXT_PACKAGE}-translations) target_include_directories(dino PRIVATE src) target_link_libraries(dino libdino ${MAIN_PACKAGES}) diff --git a/main/dino-info.rc b/main/dino-info.rc new file mode 100644 index 00000000..d8558fc5 --- /dev/null +++ b/main/dino-info.rc @@ -0,0 +1,21 @@ +1 VERSIONINFO +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080904E4" + BEGIN + VALUE "CompanyName", "Dino" + VALUE "FileDescription", "Dino - Modern XMPP (""Jabber"") Chat Client" + VALUE "InternalName", "dino" + VALUE "LegalCopyright", "Dino" + VALUE "OriginalFilename", "dino.exe" + VALUE "ProductName", "Dino" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x809, 1252 + END +END + +DINO_ICO ICON "./dino.ico" \ No newline at end of file diff --git a/main/dino.ico b/main/dino.ico new file mode 100644 index 00000000..898e4387 Binary files /dev/null and b/main/dino.ico differ diff --git a/main/src/main.vala b/main/src/main.vala index bf8759b6..0c55ae2d 100644 --- a/main/src/main.vala +++ b/main/src/main.vala @@ -9,6 +9,12 @@ namespace Dino { void main(string[] args) { try{ +#if _WIN32 + var pangocairoResult = Environment.set_variable("PANGOCAIRO_BACKEND", "fontconfig", false); + if (!pangocairoResult) { + warning("Unable to set PANGOCAIRO_BACKEND environment variable to fontconfig"); + } +#endif string? exec_path = args.length > 0 ? args[0] : null; SearchPathGenerator search_path_generator = new SearchPathGenerator(exec_path); Intl.textdomain(GETTEXT_PACKAGE); diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala index 02c9407a..f522356b 100644 --- a/main/src/ui/conversation_content_view/file_widget.vala +++ b/main/src/ui/conversation_content_view/file_widget.vala @@ -176,7 +176,7 @@ public class FileWidgetController : Object { private void open_file() { try{ - AppInfo.launch_default_for_uri(file_transfer.get_file().get_uri(), null); + Dino.Util.launch_default_for_uri(file_transfer.get_file().get_uri()); } catch (Error err) { warning("Failed to open %s - %s", file_transfer.get_file().get_uri(), err.message); } diff --git a/main/src/ui/conversation_content_view/message_widget.vala b/main/src/ui/conversation_content_view/message_widget.vala index 11b38286..5a18b51c 100644 --- a/main/src/ui/conversation_content_view/message_widget.vala +++ b/main/src/ui/conversation_content_view/message_widget.vala @@ -242,7 +242,15 @@ public class MessageMetaItem : ContentMetaItem { public static bool on_label_activate_link(string uri) { // Always handle xmpp URIs with Dino - if (!uri.has_prefix("xmpp:")) return false; + if (!uri.has_prefix("xmpp:")) { +#if _WIN32 + Dino.Util.launch_default_for_uri(uri); + return true; +#else + return false; +#endif + } + File file = File.new_for_uri(uri); Dino.Application.get_default().open(new File[]{file}, ""); return true; diff --git a/main/src/ui/file_send_overlay.vala b/main/src/ui/file_send_overlay.vala index 11bd9a11..da05d63d 100644 --- a/main/src/ui/file_send_overlay.vala +++ b/main/src/ui/file_send_overlay.vala @@ -57,7 +57,7 @@ public class FileSendOverlay { private async void load_file_widget(File file, FileInfo file_info) { string file_name = file_info.get_display_name(); - string mime_type = file_info.get_content_type(); + string mime_type = Dino.Util.get_content_type(file_info); bool is_image = false; diff --git a/main/src/ui/manage_accounts/add_account_dialog.vala b/main/src/ui/manage_accounts/add_account_dialog.vala index d7bbe66b..f589fc70 100644 --- a/main/src/ui/manage_accounts/add_account_dialog.vala +++ b/main/src/ui/manage_accounts/add_account_dialog.vala @@ -373,7 +373,7 @@ public class AddAccountDialog : Gtk.Dialog { // Button is opening a registration website if (form.oob != null) { try { - AppInfo.launch_default_for_uri(form.oob, null); + Dino.Util.launch_default_for_uri(form.oob); } catch (Error e) { } show_sign_in_jid(); return; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 03d7f575..9b8dd8dc 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,3 +1,7 @@ +if(WIN32) + add_link_options("-Wl,--export-all-symbols") +endif(WIN32) + foreach(plugin ${PLUGINS}) add_subdirectory(${plugin}) endforeach(plugin) diff --git a/plugins/openpgp/src/gpgme_helper.vala b/plugins/openpgp/src/gpgme_helper.vala index 18d07c06..956ea1c8 100644 --- a/plugins/openpgp/src/gpgme_helper.vala +++ b/plugins/openpgp/src/gpgme_helper.vala @@ -177,6 +177,13 @@ private static uint8[] get_uint8_from_data(Data data) { private static void initialize() { if (!initialized) { +#if _WIN32 + string gpg = GLib.Environment.find_program_in_path("gpg.exe"); + if (gpg != null && gpg.length > 0) + { + set_global_flag("w32-inst-dir", GLib.Path.get_dirname(gpg)); + } +#endif check_version(); initialized = true; } diff --git a/plugins/openpgp/vapi/gpgme.vapi b/plugins/openpgp/vapi/gpgme.vapi index 2fc27c65..4f09b705 100644 --- a/plugins/openpgp/vapi/gpgme.vapi +++ b/plugins/openpgp/vapi/gpgme.vapi @@ -665,6 +665,9 @@ namespace GPG { [CCode (cname = "gpgme_strerror")] public unowned string strerror(GPGError.Error err); + [CCode (cname = "gpgme_set_global_flag")] + public int set_global_flag(string name, string value); + private void throw_if_error(GPGError.Error error) throws GLib.Error { if (error.code != GPGError.ErrorCode.NO_ERROR) { throw new GLib.Error(-1, error.code, "%s", error.to_string()); diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index 98b9717d..514ffdea 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -49,6 +49,8 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { device_monitor.get_bus().add_watch(Priority.DEFAULT, on_device_monitor_message); device_monitor.start(); foreach (Gst.Device device in device_monitor.get_devices()) { + if (device.properties == null) continue; + if (device.properties.get_string("device.api") == "wasapi") continue; if (device.properties.has_name("pipewire-proplist") && device.has_classes("Audio")) continue; if (device.properties.get_string("device.class") == "monitor") continue; if (devices.any_match((it) => it.matches(device))) continue; diff --git a/plugins/win32-fonts/CMakeLists.txt b/plugins/win32-fonts/CMakeLists.txt new file mode 100644 index 00000000..3aa47ebd --- /dev/null +++ b/plugins/win32-fonts/CMakeLists.txt @@ -0,0 +1,43 @@ +find_packages(WIN32_FONTS_PACKAGES REQUIRED + Gee + GLib + GModule + GObject + GTK4 +) + +set(RESOURCE_LIST + larger.css +) + +compile_gresources( + WIN32_FONTS_GRESOURCES_TARGET + WIN32_FONTS_GRESOURCES_XML + TARGET ${CMAKE_CURRENT_BINARY_DIR}/resources/resources.c + TYPE EMBED_C + RESOURCES ${RESOURCE_LIST} + PREFIX /im/dino/Dino/win32-fonts + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data +) + +vala_precompile(WIN32_FONTS_VALA_C +SOURCES + src/plugin.vala + src/register_plugin.vala +CUSTOM_VAPIS + ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi + ${CMAKE_BINARY_DIR}/exports/dino.vapi + ${CMAKE_BINARY_DIR}/exports/qlite.vapi +PACKAGES + ${WIN32_FONTS_PACKAGES} +GRESOURCES + ${WIN32_FONTS_GRESOURCES_XML} +) + +add_definitions(${VALA_CFLAGS}) +add_library(win32-fonts SHARED ${WIN32_FONTS_VALA_C} ${WIN32_FONTS_GRESOURCES_TARGET}) +target_link_libraries(win32-fonts libdino ${WIN32_FONTS_PACKAGES}) +set_target_properties(win32-fonts PROPERTIES PREFIX "") +set_target_properties(win32-fonts PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) + +install(TARGETS win32-fonts ${PLUGIN_INSTALL}) diff --git a/plugins/win32-fonts/data/larger.css b/plugins/win32-fonts/data/larger.css new file mode 100644 index 00000000..e8983b78 --- /dev/null +++ b/plugins/win32-fonts/data/larger.css @@ -0,0 +1,3 @@ +* { + font-size: 1.125rem; +} \ No newline at end of file diff --git a/plugins/win32-fonts/src/plugin.vala b/plugins/win32-fonts/src/plugin.vala new file mode 100644 index 00000000..9d8dd5dc --- /dev/null +++ b/plugins/win32-fonts/src/plugin.vala @@ -0,0 +1,16 @@ +using Gtk; + +namespace Dino.Plugins.Win32Fonts { + +public class Plugin : RootInterface, Object { + + public void registered(Dino.Application app) { + CssProvider larger_fonts = new CssProvider(); + larger_fonts.load_from_resource("/im/dino/Dino/win32-fonts/larger.css"); + StyleContext.add_provider_for_display(Gdk.Display.get_default(), larger_fonts, STYLE_PROVIDER_PRIORITY_APPLICATION); + } + + public void shutdown() { } +} + +} diff --git a/plugins/win32-fonts/src/register_plugin.vala b/plugins/win32-fonts/src/register_plugin.vala new file mode 100644 index 00000000..b0bfa375 --- /dev/null +++ b/plugins/win32-fonts/src/register_plugin.vala @@ -0,0 +1,3 @@ +public Type register_plugin(Module module) { + return typeof (Dino.Plugins.Win32Fonts.Plugin); +} diff --git a/plugins/windows-notification/.gitignore b/plugins/windows-notification/.gitignore new file mode 100644 index 00000000..d71a29c5 --- /dev/null +++ b/plugins/windows-notification/.gitignore @@ -0,0 +1 @@ +/yolort \ No newline at end of file diff --git a/plugins/windows-notification/CMakeLists.txt b/plugins/windows-notification/CMakeLists.txt new file mode 100644 index 00000000..fdd9a706 --- /dev/null +++ b/plugins/windows-notification/CMakeLists.txt @@ -0,0 +1,77 @@ +set(GETTEXT_PACKAGE "dino-windows-notifications") +find_package(Gettext) +include(${GETTEXT_USE_FILE}) +gettext_compile(${GETTEXT_PACKAGE} SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../main/po TARGET_NAME ${GETTEXT_PACKAGE}-translations) + +project(windows-notification) + +find_packages(WINDOWS_NOTIFICATION_PACKAGES REQUIRED + Gee + GLib + GModule + GObject + GTK4 +) + +vala_precompile(WINDOWS_NOTIFICATION_VALA_C +SOURCES + src/windows_notifications_plugin.vala + src/windows_notifications_register_plugin.vala + src/toast_notification_builder.vala + src/win_notification_provider.vala +CUSTOM_VAPIS + ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi + ${CMAKE_BINARY_DIR}/exports/dino.vapi + ${CMAKE_BINARY_DIR}/exports/qlite.vapi + ${CMAKE_CURRENT_SOURCE_DIR}/vapi/win32.vapi + ${CMAKE_CURRENT_SOURCE_DIR}/vapi/winrt.vapi + ${CMAKE_CURRENT_SOURCE_DIR}/vapi/shortcutcreator.vapi + ${CMAKE_CURRENT_SOURCE_DIR}/vapi/enums.vapi + ${CMAKE_CURRENT_SOURCE_DIR}/vapi/winrt_windows_ui_notifications.vapi +PACKAGES + ${WINDOWS_NOTIFICATION_PACKAGES} +) + +set(WINDOWS_API_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/api/src/gobject/winrt-enums.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/api/src/gobject/winrt-event-token.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/api/src/gobject/winrt-toast-notification.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/api/src/gobject/winrt-toast-notifier.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/api/src/gobject/winrt.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/api/src/win32.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/api/src/converter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/api/src/dyn_mod.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/api/src/ginvoke.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/api/src/shortcutcreator.cpp +) + +add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\") +add_library(windows-notification SHARED ${WINDOWS_NOTIFICATION_VALA_C} ${WINDOWS_API_SOURCES}) +add_dependencies(windows-notification ${GETTEXT_PACKAGE}-translations) + +target_include_directories(windows-notification + PRIVATE + ${PROJECT_SOURCE_DIR}/api/include + ${PROJECT_SOURCE_DIR}/api/include/gobject + ${PROJECT_SOURCE_DIR}/yolort/include +) + +find_library(shlwapi_LIBRARY shlwapi libshlwapi libshlwapi.a HINTS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) +if(NOT shlwapi_LIBRARY) + message(FATAL_ERROR "shlwapi library not found") +endif(NOT shlwapi_LIBRARY) + +find_library(ntdll_LIBRARY ntdll libntdll libntdll.a HINTS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) +if(NOT ntdll_LIBRARY) + message(FATAL_ERROR "ntdll library not found") +endif(NOT ntdll_LIBRARY) + +target_link_libraries(windows-notification libdino ${shlwapi_LIBRARY} ${ntdll_LIBRARY} ${WINDOWS_NOTIFICATION_PACKAGES}) +target_compile_features(windows-notification PRIVATE cxx_std_17) +target_compile_definitions(windows-notification PRIVATE WINRT_GLIB_H_INSIDE) +target_compile_options(windows-notification PRIVATE $<$:-iquote ${PROJECT_SOURCE_DIR}/yolort/include/winrt/yolort_impl>) + +set_target_properties(windows-notification PROPERTIES PREFIX "") +set_target_properties(windows-notification PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) + +install(TARGETS windows-notification ${PLUGIN_INSTALL}) diff --git a/plugins/windows-notification/README.md b/plugins/windows-notification/README.md new file mode 100644 index 00000000..195307a2 --- /dev/null +++ b/plugins/windows-notification/README.md @@ -0,0 +1,10 @@ +# Windows Notification plugin for Dino +Plugin that allows native notifications on Dino on both Windows 8.1 and Windows 10. +Currently supports: +* Actionable notification (with action button on Windows 10) +* Shows avatar image + +# Special thanks +- GObject wrapper based on https://github.com/endlessm/xapian-glib/ +- YoloRT which allows compilation on GCC https://github.com/Yuubi-san/YoloRT +- Notification builder inspired by https://gitlab.gnome.org/Philipp/glib/-/commits/windows_toast_notification_backend diff --git a/plugins/windows-notification/api/include/converter.hpp b/plugins/windows-notification/api/include/converter.hpp new file mode 100644 index 00000000..1bfe21c4 --- /dev/null +++ b/plugins/windows-notification/api/include/converter.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include +#include +#include + +std::wstring sview_to_wstr(const std::string_view str); +gchar* wsview_to_char(const std::wstring_view wstr); \ No newline at end of file diff --git a/plugins/windows-notification/api/include/dyn_mod.hpp b/plugins/windows-notification/api/include/dyn_mod.hpp new file mode 100644 index 00000000..85adbb85 --- /dev/null +++ b/plugins/windows-notification/api/include/dyn_mod.hpp @@ -0,0 +1,29 @@ + +#ifndef DYN_MOD_HPP +#define DYN_MOD_HPP + +namespace dyn_mod +{ + using punny_func = void(); + + punny_func &load_symbol( + const wchar_t *mod_path, + const char *mod_dbgnym, + const char *symbol); + + template + inline T &load_symbol( + const wchar_t *const mod_path, const char *const mod_dbgnym, + const char *const symbol) + { + return reinterpret_cast(load_symbol(mod_path, mod_dbgnym, symbol)); + } +} + +#define dyn_load_symbol_ns(mod_name, namespace, symbol) \ + ::dyn_mod::load_symbol( \ + L ## mod_name, mod_name, #symbol) + +#define dyn_load_symbol(mod_name, symbol) dyn_load_symbol_ns(mod_name, ,symbol) + +#endif diff --git a/plugins/windows-notification/api/include/ginvoke.hpp b/plugins/windows-notification/api/include/ginvoke.hpp new file mode 100644 index 00000000..ef235229 --- /dev/null +++ b/plugins/windows-notification/api/include/ginvoke.hpp @@ -0,0 +1,172 @@ + +#ifndef GINVOKE_HPP +#define GINVOKE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "overload.hpp" +#include "make_array.hpp" +#include "hexify.hpp" + + +namespace glib { + +namespace impl +{ + using static_c_str = const char *; + using varstring_t = std::variant; + struct varstring : varstring_t + { + varstring(std::string &&s) noexcept : varstring_t{std::move(s)} {} + varstring(static_c_str &&s) noexcept : varstring_t{std::move(s)} {} + varstring(std::nullptr_t) = delete; + varstring(const varstring &) = delete; + varstring(varstring &&) = default; + + const char* c_str() const && = delete; + const char* c_str() const & + { + return std::visit(overload{ + [](const std::string &s){ return s.c_str(); }, + [](const static_c_str s){ return s; } + }, static_cast(*this)); + } + }; + + struct hresult + { + std::int32_t code; + varstring message; + }; + std::optional get_if_hresult_error(std::exception_ptr) noexcept; +} + +template,int> = 0> +inline auto &describe_argument(OStream &s, const T &a) +{ return s << a; } +template,int> = 0> +inline auto &describe_argument(OStream &s, const T &a) +{ return s << static_cast>(a); } + +template +inline auto &describe_argument(OStream &s, std::string_view const a) +{ return s << std::quoted(a); } +template +inline auto &describe_argument(OStream &s, const std::string & a) +{ return s << std::quoted(a); } +template +inline auto &describe_argument(OStream &s, const char * const a) +{ return s << std::quoted(a); } +// TODO: overload for const GString * + +// not implemented (TODO maybe): +template +inline auto &describe_argument(OStream &s, std::wstring_view const a) = delete; +template +inline auto &describe_argument(OStream &s, const std::wstring & a) = delete; +template +inline auto &describe_argument(OStream &s, const wchar_t * const a) = delete; + +inline impl::varstring describe_arguments() noexcept { return {""}; } + +template +inline impl::varstring describe_arguments(const Arg &... a) noexcept try +{ + std::ostringstream ss; + ((describe_argument(ss,a) << ','), ...); + auto s = std::move(ss).str(); + s.pop_back(); + return {std::move(s)}; +} +catch (...) +{ + return {""}; +} + + +#define FORMAT "%s(%s) failed: %s" +template +inline void log_invocation_failure(const char *e, + const char *func_name, const Arg &... a) noexcept +{ + const auto args = describe_arguments(a...); + g_warning(FORMAT, func_name, args.c_str(), e); +} +template +inline void log_invocation_failure_desc(const char *e, const char *e_desc, + const char *func_name, const Arg &... a) noexcept +{ + const auto args = describe_arguments(a...); + g_warning(FORMAT": %s", func_name, args.c_str(), e, e_desc); +} +#undef FORMAT + +struct regular_void {}; + +template +inline auto invoke(Invokable &&i, const Arg &... a) +{ + using R = decltype(std::invoke(std::forward(i), a...)); + if constexpr (std::is_void_v) + { + std::invoke(std::forward(i), a...); + return regular_void{}; + } + else + return std::invoke(std::forward(i), a...); +} + +template +inline auto try_invoke( + const char *func_name, Invokable &&i, const Arg &... a) noexcept + -> std::optional(i), a...))> +try +{ + return invoke(std::forward(i), a...); +} +catch (const std::exception &e) +{ + log_invocation_failure(e.what(), func_name, a...); + return {}; +} +catch (...) +{ + if (const auto e = impl::get_if_hresult_error(std::current_exception())) + { + auto hr = make_array("hresult 0x01234567\0"); + hexify32(static_cast(e->code), std::end(hr)-1); + log_invocation_failure_desc( + std::begin(hr), e->message.c_str(), func_name, a...); + } + else + log_invocation_failure("unknown error", func_name, a...); + + return {}; +} + +} // namespace glib + + +#define g_try_invoke(invokable, ...) \ + glib::try_invoke(#invokable, invokable, __VA_ARGS__) + +#define g_try_invoke0(invokable) \ + glib::try_invoke(#invokable, invokable) + +#endif diff --git a/plugins/windows-notification/api/include/gobject/winrt-enums.h b/plugins/windows-notification/api/include/gobject/winrt-enums.h new file mode 100644 index 00000000..27891a79 --- /dev/null +++ b/plugins/windows-notification/api/include/gobject/winrt-enums.h @@ -0,0 +1,61 @@ +#ifndef __WINRT_ENUMS_H__ +#define __WINRT_ENUMS_H__ + +#include + +G_BEGIN_DECLS + +#define WINRT_TYPE_WINDOWS_UI_NOTIFICATIONS_TOAST_DISMISSAL_REASON \ + (winrtWindowsUINotificationsToastDismissalReason_get_type()) + +/** + * winrt_Windows_UI_Notifications_Toast_Dismissal_Reason: + * @WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_DISMISSAL_REASON_Activated: Notification was activated, clicked or through + * a button + * @WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_DISMISSAL_REASON_ApplicationHidden: Application was hidden + * @WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_DISMISSAL_REASON_TimedOut: Notification timed out + * + * Reasons for a notification dismissal + * + */ +typedef enum { + WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_DISMISSAL_REASON_Activated, + WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_DISMISSAL_REASON_ApplicationHidden, + WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_DISMISSAL_REASON_TimedOut, +} winrtWindowsUINotificationsToastDismissalReason; + +GType winrt_windows_ui_notifications_toast_dismissal_reason_get_type(); + +#define WINRT_TYPE_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE \ + (winrt_windows_ui_notifications_toast_template_type_get_type()) + +/** + * winrtWindowsUINotificationsToastTemplateType: + * @WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastImageAndText01 + * @WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastImageAndText02 + * @WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastImageAndText03 + * @WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastImageAndText04 + * @WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastText01 + * @WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastText02 + * @WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastText03 + * @WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastText04 + * + * Basic templates for a toast notification. + * + */ +typedef enum { + WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastImageAndText01, + WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastImageAndText02, + WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastImageAndText03, + WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastImageAndText04, + WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastText01, + WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastText02, + WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastText03, + WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastText04, +} winrtWindowsUINotificationsToastTemplateType; + +GType winrt_windows_ui_notifications_toast_template_type_get_type(); + +G_END_DECLS + +#endif /* __WINRT_ENUMS_H__ */ diff --git a/plugins/windows-notification/api/include/gobject/winrt-event-token-private.h b/plugins/windows-notification/api/include/gobject/winrt-event-token-private.h new file mode 100644 index 00000000..9f403104 --- /dev/null +++ b/plugins/windows-notification/api/include/gobject/winrt-event-token-private.h @@ -0,0 +1,12 @@ +#ifndef __WINRT_GLIB_EVENTTOKEN_PRIVATE_H__ +#define __WINRT_GLIB_EVENTTOKEN_PRIVATE_H__ + +#include +#include "winrt-headers.h" + +#include "winrt-event-token.h" + +winrtEventToken* winrt_event_token_new_from_token(winrt::event_token* token); +winrt::event_token* winrt_event_token_get_internal(winrtEventToken* self); + +#endif /* __WINRT_GLIB_EVENTTOKEN_PRIVATE_H__ */ diff --git a/plugins/windows-notification/api/include/gobject/winrt-event-token.h b/plugins/windows-notification/api/include/gobject/winrt-event-token.h new file mode 100644 index 00000000..f1c6c1ca --- /dev/null +++ b/plugins/windows-notification/api/include/gobject/winrt-event-token.h @@ -0,0 +1,36 @@ +#ifndef __WINRT_GLIB_EVENTTOKEN_H__ +#define __WINRT_GLIB_EVENTTOKEN_H__ + +#if !defined(WINRT_GLIB_H_INSIDE) && !defined(WINRT_GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include "winrt-glib-types.h" + +G_BEGIN_DECLS + +#define WINRT_TYPE_EVENT_TOKEN (winrt_event_token_get_type()) + +G_DECLARE_DERIVABLE_TYPE (winrtEventToken, winrt_event_token, WINRT, EVENT_TOKEN, GObject) + +struct _winrtEventTokenClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +gint64 winrt_event_token_get_value(winrtEventToken* self); +gboolean winrt_event_token_operator_bool(winrtEventToken* self); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* __WINRT_GLIB_EVENTTOKEN_H__ */ diff --git a/plugins/windows-notification/api/include/gobject/winrt-glib-types.h b/plugins/windows-notification/api/include/gobject/winrt-glib-types.h new file mode 100644 index 00000000..02f6eb0d --- /dev/null +++ b/plugins/windows-notification/api/include/gobject/winrt-glib-types.h @@ -0,0 +1,7 @@ +#ifndef __WINRT_GLIB_TYPES_H__ +#define __WINRT_GLIB_TYPES_H__ + +#include +#include "winrt-enums.h" + +#endif /* __WINRT_GLIB_TYPES_H__ */ diff --git a/plugins/windows-notification/api/include/gobject/winrt-glib.h b/plugins/windows-notification/api/include/gobject/winrt-glib.h new file mode 100644 index 00000000..e520cca1 --- /dev/null +++ b/plugins/windows-notification/api/include/gobject/winrt-glib.h @@ -0,0 +1,18 @@ +#ifndef __WINRT_GLIB_H__ +#define __WINRT_GLIB_H__ + +#ifndef WINRT_GLIB_H_INSIDE +#define WINRT_GLIB_H_INSIDE +#endif + +#include "winrt-enums.h" +#include "winrt-glib-types.h" + +#include "winrt.h" +#include "winrt-toast-notification.h" +#include "winrt-event-token.h" +#include "winrt-toast-notifier.h" + +#undef WINRT_GLIB_H_INSIDE + +#endif /* __`WINRT_GLIB_H__ */ diff --git a/plugins/windows-notification/api/include/gobject/winrt-headers.h b/plugins/windows-notification/api/include/gobject/winrt-headers.h new file mode 100644 index 00000000..e5428c83 --- /dev/null +++ b/plugins/windows-notification/api/include/gobject/winrt-headers.h @@ -0,0 +1,9 @@ +#ifndef __WINRT_HEADERS_H__ +#define __WINRT_HEADERS_H__ + +#include +#include +#include +#include + +#endif /* __WINRT_HEADERS_H__ */ diff --git a/plugins/windows-notification/api/include/gobject/winrt-private.h b/plugins/windows-notification/api/include/gobject/winrt-private.h new file mode 100644 index 00000000..a5249325 --- /dev/null +++ b/plugins/windows-notification/api/include/gobject/winrt-private.h @@ -0,0 +1,8 @@ +#ifndef __WINRT_GLIB_H_PRIVATE__ +#define __WINRT_GLIB_H_PRIVATE__ + +#include +#include "winrt.h" +#include "gobject/winrt-headers.h" + +#endif // __WINRT_GLIB_H_PRIVATE__ \ No newline at end of file diff --git a/plugins/windows-notification/api/include/gobject/winrt-toast-notification-private.h b/plugins/windows-notification/api/include/gobject/winrt-toast-notification-private.h new file mode 100644 index 00000000..b09fc7a4 --- /dev/null +++ b/plugins/windows-notification/api/include/gobject/winrt-toast-notification-private.h @@ -0,0 +1,12 @@ +#ifndef __WINRT_GLIB_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION_PRIVATE_H__ +#define __WINRT_GLIB_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION_PRIVATE_H__ + +#include +#include "winrt-headers.h" + +#include "winrt-toast-notification.h" + +winrt::Windows::UI::Notifications::ToastNotification* winrt_windows_ui_notifications_toast_notification_get_internal(winrtWindowsUINotificationsToastNotification* self); +void winrt_windows_ui_notifications_toast_notification_set_internal(winrtWindowsUINotificationsToastNotification *self, winrt::Windows::UI::Notifications::ToastNotification notification); + +#endif /* __WINRT_GLIB_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION_PRIVATE_H__ */ diff --git a/plugins/windows-notification/api/include/gobject/winrt-toast-notification.h b/plugins/windows-notification/api/include/gobject/winrt-toast-notification.h new file mode 100644 index 00000000..eb5ccdf8 --- /dev/null +++ b/plugins/windows-notification/api/include/gobject/winrt-toast-notification.h @@ -0,0 +1,58 @@ +#ifndef __WINRT_GLIB_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION_H__ +#define __WINRT_GLIB_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION_H__ + +#if !defined(WINRT_GLIB_H_INSIDE) && !defined(WINRT_GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include "winrt-glib-types.h" +#include "winrt-event-token.h" + +G_BEGIN_DECLS + +#define WINRT_TYPE_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (winrt_windows_ui_notifications_toast_notification_get_type()) + +G_DECLARE_DERIVABLE_TYPE (winrtWindowsUINotificationsToastNotification, winrt_windows_ui_notifications_toast_notification, WINRT, WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION, GObject) + +struct _winrtWindowsUINotificationsToastNotificationClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef void(*NotificationCallbackFailed)(void* userdata); +typedef void(*NotificationCallbackActivated)(const gchar* arguments, gchar** userInput, gint count, void* userdata); +typedef void(*NotificationCallbackDismissed)(winrtWindowsUINotificationsToastDismissalReason reason, void* userdata); + +winrtWindowsUINotificationsToastNotification* winrt_windows_ui_notifications_toast_notification_new(const gchar* doc); + +void winrt_windows_ui_notifications_toast_notification_set_ExpiresOnReboot(winrtWindowsUINotificationsToastNotification* self, gboolean value); +gboolean winrt_windows_ui_notifications_toast_notification_get_ExpiresOnReboot(winrtWindowsUINotificationsToastNotification* self); + +void winrt_windows_ui_notifications_toast_notification_SetTag(winrtWindowsUINotificationsToastNotification* self, const gchar* value); +gchar* winrt_windows_ui_notifications_toast_notification_GetTag(winrtWindowsUINotificationsToastNotification* self); + +void winrt_windows_ui_notifications_toast_notification_SetGroup(winrtWindowsUINotificationsToastNotification* self, const gchar* value); +gchar* winrt_windows_ui_notifications_toast_notification_GetGroup(winrtWindowsUINotificationsToastNotification* self); + +winrtEventToken* winrt_windows_ui_notifications_toast_notification_Activated(winrtWindowsUINotificationsToastNotification* self, NotificationCallbackActivated callback, void* context, void(*free)(void*)); +void winrt_windows_ui_notifications_toast_notification_RemoveActivated(winrtWindowsUINotificationsToastNotification* self, winrtEventToken* token); + +winrtEventToken* winrt_windows_ui_notifications_toast_notification_Failed(winrtWindowsUINotificationsToastNotification* self, NotificationCallbackFailed callback, void* context, void(*free)(void*)); +void winrt_windows_ui_notifications_toast_notification_RemoveFailed(winrtWindowsUINotificationsToastNotification* self, winrtEventToken* token); + +winrtEventToken* winrt_windows_ui_notifications_toast_notification_Dismissed(winrtWindowsUINotificationsToastNotification* self, NotificationCallbackDismissed callback, void* context, void(*free)(void*)); +void winrt_windows_ui_notifications_toast_notification_RemoveDismissed(winrtWindowsUINotificationsToastNotification* self, winrtEventToken* token); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* __WINRT_GLIB_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION_H__ */ diff --git a/plugins/windows-notification/api/include/gobject/winrt-toast-notifier-private.h b/plugins/windows-notification/api/include/gobject/winrt-toast-notifier-private.h new file mode 100644 index 00000000..fb695445 --- /dev/null +++ b/plugins/windows-notification/api/include/gobject/winrt-toast-notifier-private.h @@ -0,0 +1,12 @@ +#ifndef __WINRT_GLIB_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFIER_PRIVATE_H__ +#define __WINRT_GLIB_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFIER_PRIVATE_H__ + +#include +#include "winrt-headers.h" + +#include "winrt-toast-notifier.h" + +winrt::Windows::UI::Notifications::ToastNotifier* winrt_windows_ui_notifications_toast_notifier_get_internal(winrtWindowsUINotificationsToastNotifier* self); +void winrt_windows_ui_notifications_toast_notifier_set_internal(winrtWindowsUINotificationsToastNotifier *self, winrt::Windows::UI::Notifications::ToastNotifier notification); + +#endif /* __WINRT_GLIB_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFIER_PRIVATE_H__ */ diff --git a/plugins/windows-notification/api/include/gobject/winrt-toast-notifier.h b/plugins/windows-notification/api/include/gobject/winrt-toast-notifier.h new file mode 100644 index 00000000..3473c886 --- /dev/null +++ b/plugins/windows-notification/api/include/gobject/winrt-toast-notifier.h @@ -0,0 +1,39 @@ +#ifndef __WINRT_GLIB_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFIER_H__ +#define __WINRT_GLIB_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFIER_H__ + +#if !defined(WINRT_GLIB_H_INSIDE) && !defined(WINRT_GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include "winrt-glib-types.h" +#include "winrt-toast-notification.h" + +G_BEGIN_DECLS + +#define WINRT_TYPE_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFIER (winrt_windows_ui_notifications_toast_notifier_get_type()) + +G_DECLARE_DERIVABLE_TYPE (winrtWindowsUINotificationsToastNotifier, winrt_windows_ui_notifications_toast_notifier, WINRT, WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFIER, GObject) + +struct _winrtWindowsUINotificationsToastNotifierClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +winrtWindowsUINotificationsToastNotifier* winrt_windows_ui_notifications_toast_notifier_new(const gchar* aumid); + +void winrt_windows_ui_notifications_toast_notifier_Show(winrtWindowsUINotificationsToastNotifier* self, winrtWindowsUINotificationsToastNotification* toast_notification); +void winrt_windows_ui_notifications_toast_notifier_Hide(winrtWindowsUINotificationsToastNotifier* self, winrtWindowsUINotificationsToastNotification* toast_notification); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* __WINRT_GLIB_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFIER_H__ */ diff --git a/plugins/windows-notification/api/include/gobject/winrt.h b/plugins/windows-notification/api/include/gobject/winrt.h new file mode 100644 index 00000000..bb2ce728 --- /dev/null +++ b/plugins/windows-notification/api/include/gobject/winrt.h @@ -0,0 +1,24 @@ +#ifndef __WINRT_GLIB_2_H__ +#define __WINRT_GLIB_2_H__ + +#if !defined(WINRT_GLIB_H_INSIDE) && !defined(WINRT_GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include "winrt-enums.h" + +#ifdef __cplusplus +#define EXTERN extern "C" +#define NOEXCEPT noexcept +#else +#define EXTERN +#define NOEXCEPT +#endif + +EXTERN gboolean winrt_InitApartment() NOEXCEPT; +EXTERN char* winrt_windows_ui_notifications_toast_notification_manager_GetTemplateContent(winrtWindowsUINotificationsToastTemplateType type) NOEXCEPT; + +#undef EXTERN +#undef NOEXCEPT + +#endif // __WINRT_GLIB_2_H__ diff --git a/plugins/windows-notification/api/include/hexify.hpp b/plugins/windows-notification/api/include/hexify.hpp new file mode 100644 index 00000000..e1c90a0d --- /dev/null +++ b/plugins/windows-notification/api/include/hexify.hpp @@ -0,0 +1,12 @@ + +#ifndef HEXIFY_HPP +#define HEXIFY_HPP +#include + +constexpr void hexify32(std::uint32_t val, char *const end) noexcept +{ + auto p = end-1; + for (auto i = 0; i < 32/4; ++i, --p, val >>= 4) + *p = "0123456789ABCDEF"[val & ((1u<<4)-1u)]; +} +#endif diff --git a/plugins/windows-notification/api/include/make_array.hpp b/plugins/windows-notification/api/include/make_array.hpp new file mode 100644 index 00000000..cf7cdf35 --- /dev/null +++ b/plugins/windows-notification/api/include/make_array.hpp @@ -0,0 +1,15 @@ + +#ifndef MAKE_ARRAY_HPP +#define MAKE_ARRAY_HPP +#include +#include + +template +inline auto make_array(const char (&from_literal)[N]) noexcept +{ + static_assert(N); + std::array a; + std::copy(+from_literal, from_literal+a.size(), a.begin()); + return a; +} +#endif diff --git a/plugins/windows-notification/api/include/notificationhandler.hpp b/plugins/windows-notification/api/include/notificationhandler.hpp new file mode 100644 index 00000000..1f961c6b --- /dev/null +++ b/plugins/windows-notification/api/include/notificationhandler.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "typedefinitions.h" + +class NotificationHandler { +private: + dinoWinToastLib_Notification_Callbacks callbacks{}; + +public: + WinToastHandler(dinoWinToastLib_Notification_Callbacks callbacks); + ~WinToastHandler(); + + // Public interfaces + void toastActivated() const; + void toastActivated(int actionIndex) const; + void toastDismissed(WinToastLib::IWinToastHandler::WinToastDismissalReason state) const; + void toastFailed() const; +}; \ No newline at end of file diff --git a/plugins/windows-notification/api/include/overload.hpp b/plugins/windows-notification/api/include/overload.hpp new file mode 100644 index 00000000..902852b5 --- /dev/null +++ b/plugins/windows-notification/api/include/overload.hpp @@ -0,0 +1,10 @@ + +#ifndef OVERLOAD_HPP +#define OVERLOAD_HPP +template +struct overload : Callable... +{ + overload(Callable &&... c) : Callable{std::move(c)}... {} + using Callable::operator()...; +}; +#endif diff --git a/plugins/windows-notification/api/include/shortcutcreator.h b/plugins/windows-notification/api/include/shortcutcreator.h new file mode 100644 index 00000000..d7ba598c --- /dev/null +++ b/plugins/windows-notification/api/include/shortcutcreator.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#ifdef __cplusplus +#define EXTERN extern "C" +#define NOEXCEPT noexcept +#else +#define EXTERN +#define NOEXCEPT +#endif + +EXTERN gboolean EnsureAumiddedShortcutExists(const gchar* aumid) NOEXCEPT; + +#undef EXTERN +#undef NOEXCEPT diff --git a/plugins/windows-notification/api/include/win32.h b/plugins/windows-notification/api/include/win32.h new file mode 100644 index 00000000..ccf090f7 --- /dev/null +++ b/plugins/windows-notification/api/include/win32.h @@ -0,0 +1 @@ +#include "win32.hpp" \ No newline at end of file diff --git a/plugins/windows-notification/api/include/win32.hpp b/plugins/windows-notification/api/include/win32.hpp new file mode 100644 index 00000000..34b9874c --- /dev/null +++ b/plugins/windows-notification/api/include/win32.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +#ifdef __cplusplus +#include +#include +#include +#include + +#include "make_array.hpp" +#include "hexify.hpp" + +struct win32_error : std::exception +{ + std::uint32_t code; + explicit win32_error() noexcept; // initializes with GetLastError() + explicit win32_error(const std::uint32_t code) noexcept + : code{code} + {} + const char *what() const noexcept override + { + // NOTE: thread-unsafe + // TODO: decimal representation seems to be more usual for win32 errors + msg = make_array("win32 error 0x01234567\0"); + hexify32(code, std::end(msg)-1); + return std::data(msg); + } +private: + mutable std::array msg; +}; + +std::wstring GetExePath(); +std::wstring GetEnv(const wchar_t *variable_name); + +#define EXTERN extern "C" +#define NOEXCEPT noexcept +#else +#define EXTERN +#define NOEXCEPT +#endif + +EXTERN gboolean IsWindows10() NOEXCEPT; +EXTERN gboolean SetProcessAumid(const gchar* aumid) NOEXCEPT; + +#undef EXTERN +#undef NOEXCEPT diff --git a/plugins/windows-notification/api/src/converter.cpp b/plugins/windows-notification/api/src/converter.cpp new file mode 100644 index 00000000..634ef0c4 --- /dev/null +++ b/plugins/windows-notification/api/src/converter.cpp @@ -0,0 +1,29 @@ +#include + +#include "converter.hpp" + +// Convert a wide Unicode string to an UTF8 string +char* wsview_to_char(const std::wstring_view wstr) +{ + if(wstr.empty()) + { + return nullptr; + } + int final_size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr); + gchar* strTo = g_new0(gchar, final_size + 1); + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), strTo, final_size, nullptr, nullptr); + return strTo; +} + +// Convert an UTF8 string to a wide Unicode String +std::wstring sview_to_wstr(const std::string_view str) +{ + if(str.empty()) + { + return std::wstring(); + } + int final_size = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); + std::wstring wstrTo(final_size, 0); + MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), wstrTo.data(), final_size); + return wstrTo; +} \ No newline at end of file diff --git a/plugins/windows-notification/api/src/dyn_mod.cpp b/plugins/windows-notification/api/src/dyn_mod.cpp new file mode 100644 index 00000000..979a48b3 --- /dev/null +++ b/plugins/windows-notification/api/src/dyn_mod.cpp @@ -0,0 +1,32 @@ + +#include "dyn_mod.hpp" +#include "win32.hpp" + +#include +#include + +namespace dyn_mod +{ + auto load_module(const wchar_t *const path, const char *const dbgnym) + { + const auto mod = ::LoadLibraryW(path); + if (mod) + return mod; + const win32_error e{}; + g_warning("failed to load %s", dbgnym); + throw e; + } + + punny_func &load_symbol( + const wchar_t *const mod_path, const char *const mod_dbgnym, + const char *const symbol) + { + const auto p = reinterpret_cast( + ::GetProcAddress(load_module(mod_path, mod_dbgnym), symbol)); + if (p) + return *p; + const win32_error e{}; + g_warning("couldn't find %s in %s", symbol, mod_dbgnym); + throw e; + } +} diff --git a/plugins/windows-notification/api/src/ginvoke.cpp b/plugins/windows-notification/api/src/ginvoke.cpp new file mode 100644 index 00000000..cb792c06 --- /dev/null +++ b/plugins/windows-notification/api/src/ginvoke.cpp @@ -0,0 +1,45 @@ + +#include "ginvoke.hpp" +#include "converter.hpp" + +#include + + +namespace glib::impl +{ + std::optional get_if_hresult_error( + const std::exception_ptr p) noexcept try + { + std::rethrow_exception(p); + } + catch (const winrt::hresult_error &e) + { + const char *ptr = nullptr; + try + { + const auto wmsg = std::wstring_view{e.message()}; + if (not wmsg.empty()) + { + ptr = wsview_to_char(wmsg); + if (not ptr) + throw 42; + std::string msg{ptr}; + g_free(const_cast(ptr)); + // ^ WTF? Deletion is not modification! ^ + return {{ e.code(), std::move(msg) }}; + } + else + return {{ e.code(), "" }}; + } + catch (...) + { + g_free(const_cast(ptr)); + return {{ e.code(), "" }}; + } + } + catch (...) + { + // This is not the exception you are looking for. + return {}; + } +} diff --git a/plugins/windows-notification/api/src/gobject/winrt-enums.cpp b/plugins/windows-notification/api/src/gobject/winrt-enums.cpp new file mode 100644 index 00000000..aff82329 --- /dev/null +++ b/plugins/windows-notification/api/src/gobject/winrt-enums.cpp @@ -0,0 +1,31 @@ +#include "winrt-enums.h" + +#define WINRT_GLIB_DEFINE_ENUM_VALUE(value, nick) \ + { value, #value, nick }, + +#define WINRT_GLIB_DEFINE_ENUM_TYPE(TypeName, type_name, values) \ +GType type_name##_get_type() \ +{ \ + static constexpr GEnumValue v[] = { \ + values \ + { 0, NULL, NULL }, \ + }; \ + static const auto enum_type_id = \ + g_enum_register_static(g_intern_static_string(#TypeName), v); \ + return enum_type_id; \ +} + +WINRT_GLIB_DEFINE_ENUM_TYPE (winrtWindowsUINotificationsToastDismissalReason, winrt_windows_ui_notifications_toast_dismissal_reason, + WINRT_GLIB_DEFINE_ENUM_VALUE (WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_DISMISSAL_REASON_Activated, "activated") + WINRT_GLIB_DEFINE_ENUM_VALUE (WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_DISMISSAL_REASON_ApplicationHidden, "application-hidden") + WINRT_GLIB_DEFINE_ENUM_VALUE (WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_DISMISSAL_REASON_TimedOut, "timed-out")) + +WINRT_GLIB_DEFINE_ENUM_TYPE (winrtWindowsUINotificationsToastTemplateType, winrt_windows_ui_notifications_toast_template_type, + WINRT_GLIB_DEFINE_ENUM_VALUE (WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastImageAndText01, "toast-image-and-text01") + WINRT_GLIB_DEFINE_ENUM_VALUE (WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastImageAndText02, "toast-image-and-text02") + WINRT_GLIB_DEFINE_ENUM_VALUE (WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastImageAndText03, "toast-image-and-text03") + WINRT_GLIB_DEFINE_ENUM_VALUE (WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastImageAndText04, "toast-image-and-text04") + WINRT_GLIB_DEFINE_ENUM_VALUE (WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastText01, "toast-text01") + WINRT_GLIB_DEFINE_ENUM_VALUE (WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastText02, "toast-text02") + WINRT_GLIB_DEFINE_ENUM_VALUE (WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastText03, "toast-text03") + WINRT_GLIB_DEFINE_ENUM_VALUE (WINRT_WINDOWS_UI_NOTIFICATIONS_TOAST_TEMPLATE_TYPE_ToastText04, "toast-text04")) diff --git a/plugins/windows-notification/api/src/gobject/winrt-event-token.cpp b/plugins/windows-notification/api/src/gobject/winrt-event-token.cpp new file mode 100644 index 00000000..df912e86 --- /dev/null +++ b/plugins/windows-notification/api/src/gobject/winrt-event-token.cpp @@ -0,0 +1,76 @@ +#include "winrt-event-token-private.h" + +#define WINRT_EVENT_TOKEN_GET_PRIVATE(obj) \ + ((winrtEventTokenPrivate*) winrt_event_token_get_instance_private ((winrtEventToken*) (obj))) + +typedef struct +{ + winrt::event_token* token; +} winrtEventTokenPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (winrtEventToken, winrt_event_token, G_TYPE_OBJECT) + +static void winrt_event_token_finalize(GObject* self) +{ + winrtEventTokenPrivate* priv = WINRT_EVENT_TOKEN_GET_PRIVATE (self); + + delete priv->token; + + G_OBJECT_CLASS(winrt_event_token_parent_class)->dispose(self); +} + +static void winrt_event_token_class_init (winrtEventTokenClass* klass) +{ + GObjectClass* gobject_class = G_OBJECT_CLASS(klass); + + gobject_class->finalize = winrt_event_token_finalize; +} + +static void winrt_event_token_init (winrtEventToken */*self*/) +{ +} + +/*< private > + * winrt_event_token_get_internal: + * @self: a #winrtEventToken + * + * Retrieves the `winrt::Windows::UI::Notifications::ToastNotification` object used by @self. + * + * Returns: (transfer none): a pointer to the internal toast notification instance + */ +winrt::event_token* winrt_event_token_get_internal(winrtEventToken *self) +{ + winrtEventTokenPrivate *priv = WINRT_EVENT_TOKEN_GET_PRIVATE(self); + + return priv->token; +} + +/*< private > + * winrt_event_token_new: + * @doc: the document to be shown + * + * Creates a new toast notification with a document already set. + * + * Returns: (transfer full): the newly created #winrtEventToken instance + */ +winrtEventToken* winrt_event_token_new_from_token(winrt::event_token* token) +{ + auto ret = static_cast(g_object_new (WINRT_TYPE_EVENT_TOKEN, NULL)); + winrtEventTokenPrivate* priv = WINRT_EVENT_TOKEN_GET_PRIVATE(ret); + priv->token = new winrt::event_token(*token); + return ret; +} + +gboolean winrt_event_token_operator_bool(winrtEventToken* self) +{ + g_return_val_if_fail(WINRT_IS_EVENT_TOKEN(self), FALSE); + + return winrt_event_token_get_internal(self)->operator bool(); +} + +gint64 winrt_event_token_get_value(winrtEventToken* self) +{ + g_return_val_if_fail (WINRT_IS_EVENT_TOKEN (self), 0); + + return winrt_event_token_get_internal(self)->value; +} diff --git a/plugins/windows-notification/api/src/gobject/winrt-toast-notification.cpp b/plugins/windows-notification/api/src/gobject/winrt-toast-notification.cpp new file mode 100644 index 00000000..32411e7d --- /dev/null +++ b/plugins/windows-notification/api/src/gobject/winrt-toast-notification.cpp @@ -0,0 +1,383 @@ +#include +#include +#include +#include +#include +#include + +#include "winrt-toast-notification-private.h" +#include "winrt-event-token-private.h" +#include "converter.hpp" + +template +inline void erase_if(Cont &c, Pred p) +{ + c.erase(std::remove_if(c.begin(), c.end(), std::move(p)), c.end()); +} + +#define WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFICATION_GET_PRIVATE(obj) \ + ((winrtWindowsUINotificationsToastNotificationPrivate*) winrt_windows_ui_notifications_toast_notification_get_instance_private ((winrtWindowsUINotificationsToastNotification*) (obj))) + +template +struct Callback { +private: + winrtEventToken* token; + +public: + T callback; + void* context; + void(*free)(void*); + + + Callback(T callback, void* context, void(*free)(void*)) + : token {} + , callback{callback} + , context {context} + , free {free} + {} + + ~Callback() + { + if (this->callback && this->context && this->free) + { + this->free(this->context); + } + + if (this->token) { + g_object_unref(this->token); + } + + this->callback = nullptr; + this->context = nullptr; + this->free = nullptr; + this->token = nullptr; + } + + void SetToken(winrtEventToken* token) { + this->token = token; + g_object_ref(this->token); + } + + winrtEventToken* GetToken() { + return this->token; + } + + // delete copy + Callback(const Callback&) = delete; + Callback& operator=(const Callback&) = delete; + + // delete move + Callback(Callback&&) = delete; + Callback& operator=(Callback&&) = delete; +}; + +struct _winrtWindowsUINotificationsToastNotificationPrivate +{ + winrt::Windows::UI::Notifications::ToastNotification data; + + std::vector>> activated{}; + std::vector>> failed{}; + std::vector>> dismissed{}; +}; + +typedef struct +{ + _winrtWindowsUINotificationsToastNotificationPrivate* notification; +} winrtWindowsUINotificationsToastNotificationPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (winrtWindowsUINotificationsToastNotification, winrt_windows_ui_notifications_toast_notification, G_TYPE_OBJECT) + +static void winrt_windows_ui_notifications_toast_notification_finalize(GObject* self) +{ + winrtWindowsUINotificationsToastNotificationPrivate* priv = WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFICATION_GET_PRIVATE (self); + + for (const auto& item : priv->notification->activated) + { + auto token = item->GetToken(); + if (winrt_event_token_operator_bool(token)) + { + priv->notification->data.Activated(*winrt_event_token_get_internal(token)); + } + } + + for (const auto& item : priv->notification->failed) + { + auto token = item->GetToken(); + if (winrt_event_token_operator_bool(token)) + { + priv->notification->data.Failed(*winrt_event_token_get_internal(token)); + } + } + + for (const auto& item : priv->notification->dismissed) + { + auto token = item->GetToken(); + if (winrt_event_token_operator_bool(token)) + { + priv->notification->data.Dismissed(*winrt_event_token_get_internal(token)); + } + } + + delete priv->notification; + + G_OBJECT_CLASS(winrt_windows_ui_notifications_toast_notification_parent_class)->dispose(self); +} + +static void winrt_windows_ui_notifications_toast_notification_class_init (winrtWindowsUINotificationsToastNotificationClass* klass) +{ + GObjectClass* gobject_class = G_OBJECT_CLASS(klass); + + gobject_class->finalize = winrt_windows_ui_notifications_toast_notification_finalize; +} + +static void winrt_windows_ui_notifications_toast_notification_init (winrtWindowsUINotificationsToastNotification */*self*/) +{ +} + +/*< private > + * winrt_windows_ui_notifications_toast_notification_get_internal: + * @self: a #winrtWindowsUINotificationsToastNotification + * + * Retrieves the `winrt::Windows::UI::Notifications::ToastNotification` object used by @self. + * + * Returns: (transfer none): a pointer to the internal toast notification instance + */ +winrt::Windows::UI::Notifications::ToastNotification* winrt_windows_ui_notifications_toast_notification_get_internal(winrtWindowsUINotificationsToastNotification *self) +{ + winrtWindowsUINotificationsToastNotificationPrivate *priv = WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFICATION_GET_PRIVATE (self); + + return &priv->notification->data; +} + +/*< private > + * winrt_windows_ui_notifications_toast_notification_set_internal: + * @self: a #winrtWindowsUINotificationsToastNotification + * @notification: a `winrt::Windows::UI::Notifications::ToastNotification` instance + * + * Sets the internal database instance wrapped by @self, clearing + * any existing instance if needed. + */ +void winrt_windows_ui_notifications_toast_notification_set_internal(winrtWindowsUINotificationsToastNotification* self, winrt::Windows::UI::Notifications::ToastNotification notification) +{ + winrtWindowsUINotificationsToastNotificationPrivate* priv = WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFICATION_GET_PRIVATE(self); + + delete priv->notification; + + priv->notification = new _winrtWindowsUINotificationsToastNotificationPrivate { notification }; +} + +/** + * winrt_windows_ui_notifications_toast_notification_new: + * @doc: the document to be shown + * + * Creates a new toast notification with a document already set. + * + * Returns: (transfer full): the newly created #winrtWindowsUINotificationsToastNotification instance + */ +winrtWindowsUINotificationsToastNotification* winrt_windows_ui_notifications_toast_notification_new(const char* doc) +{ + g_return_val_if_fail (doc != NULL, NULL); + + auto ret = static_cast(g_object_new (WINRT_TYPE_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION, NULL)); + winrt::Windows::Data::Xml::Dom::XmlDocument xmlDoc; + xmlDoc.LoadXml(sview_to_wstr(doc)); + winrt_windows_ui_notifications_toast_notification_set_internal(ret, winrt::Windows::UI::Notifications::ToastNotification{ xmlDoc }); + return ret; +} + +void winrt_windows_ui_notifications_toast_notification_set_ExpiresOnReboot(winrtWindowsUINotificationsToastNotification* self, gboolean value) +{ + g_return_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (self)); + + winrt_windows_ui_notifications_toast_notification_get_internal(self)->ExpiresOnReboot(value); +} + +gboolean winrt_windows_ui_notifications_toast_notification_get_ExpiresOnReboot(winrtWindowsUINotificationsToastNotification* self) +{ + g_return_val_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (self), FALSE); + + return winrt_windows_ui_notifications_toast_notification_get_internal(self)->ExpiresOnReboot(); +} + +void winrt_windows_ui_notifications_toast_notification_SetTag(winrtWindowsUINotificationsToastNotification* self, const char* value) +{ + g_return_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (self)); + + winrt_windows_ui_notifications_toast_notification_get_internal(self)->Tag(sview_to_wstr(value)); +} + +/** + * winrt_windows_ui_notifications_toast_notification_GetTag: + * @manager: a #winrtWindowsUINotificationsToastNotification + * + * Returns the value of the tag + * + * Returns: (transfer full): the value + */ +char* winrt_windows_ui_notifications_toast_notification_GetTag(winrtWindowsUINotificationsToastNotification* self) +{ + g_return_val_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (self), nullptr); + + return wsview_to_char(winrt_windows_ui_notifications_toast_notification_get_internal(self)->Tag()); +} + +void winrt_windows_ui_notifications_toast_notification_SetGroup(winrtWindowsUINotificationsToastNotification* self, const char* value) +{ + g_return_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (self)); + + winrt_windows_ui_notifications_toast_notification_get_internal(self)->Group(sview_to_wstr(value)); +} + +/** + * winrt_windows_ui_notifications_toast_notification_GetGroup: + * @manager: a #winrtWindowsUINotificationsToastNotification + * + * Returns the value of the group + * + * Returns: (transfer full): the value + */ +char* winrt_windows_ui_notifications_toast_notification_GetGroup(winrtWindowsUINotificationsToastNotification* self) +{ + g_return_val_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (self), nullptr); + + return wsview_to_char(winrt_windows_ui_notifications_toast_notification_get_internal(self)->Group()); +} + +winrtEventToken* winrt_windows_ui_notifications_toast_notification_Activated(winrtWindowsUINotificationsToastNotification* self, NotificationCallbackActivated callback, void* context, void(*free)(void*)) +{ + g_return_val_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (self), NULL); + g_return_val_if_fail (callback != nullptr && context != nullptr && free != nullptr, NULL); + + winrtWindowsUINotificationsToastNotificationPrivate* priv = WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFICATION_GET_PRIVATE(self); + + auto callback_data = std::make_shared>(callback, context, free); + auto token = priv->notification->data.Activated([=](auto /*sender*/, winrt::Windows::Foundation::IInspectable inspectable) + { + std::wstring arguments; + std::vector> user_input; + { + auto args = inspectable.try_as(); + if (args != nullptr) + { + arguments = std::wstring(args.Arguments()); + } + } + + { + auto args = inspectable.try_as(); + if (args != nullptr) + { + for (const auto& item : args.UserInput()) + { + auto value = winrt::unbox_value_or(item.Value(), winrt::hstring()); + user_input.emplace_back(std::make_tuple(std::wstring(item.Key()), std::wstring(value))); + } + } + } + + auto args = wsview_to_char(arguments); + callback_data->callback(args, nullptr /* user_input */ , 0 /* user_input.size() */, callback_data->context); + g_free(args); + }); + callback_data->SetToken(winrt_event_token_new_from_token(&token)); + + priv->notification->activated.push_back(callback_data); + return callback_data->GetToken(); +} + +winrtEventToken* winrt_windows_ui_notifications_toast_notification_Failed(winrtWindowsUINotificationsToastNotification* self, NotificationCallbackFailed callback, void* context, void(*free)(void*)) +{ + g_return_val_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (self), NULL); + g_return_val_if_fail (callback != nullptr && context != nullptr && free != nullptr, NULL); + + winrtWindowsUINotificationsToastNotificationPrivate* priv = WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFICATION_GET_PRIVATE(self); + + auto callback_data = std::make_shared>(callback, context, free); + auto token = priv->notification->data.Failed([=](auto /*sender*/, auto /*toastFailedEventArgs*/) + { + callback_data->callback(callback_data->context); + }); + + callback_data->SetToken(winrt_event_token_new_from_token(&token)); + + priv->notification->failed.push_back(callback_data); + return callback_data->GetToken(); +} + +winrtEventToken* winrt_windows_ui_notifications_toast_notification_Dismissed(winrtWindowsUINotificationsToastNotification* self, NotificationCallbackDismissed callback, void* context, void(*free)(void*)) +{ + g_return_val_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (self), NULL); + g_return_val_if_fail (callback != nullptr && context != nullptr && free != nullptr, NULL); + + winrtWindowsUINotificationsToastNotificationPrivate* priv = WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFICATION_GET_PRIVATE(self); + + auto callback_data = std::make_shared>(callback, context, free); + auto token = priv->notification->data.Dismissed([=](auto /*sender*/, winrt::Windows::UI::Notifications::ToastDismissedEventArgs dismissed) + { + auto reason = dismissed.Reason(); + callback_data->callback(static_cast(reason), callback_data->context); + }); + + callback_data->SetToken(winrt_event_token_new_from_token(&token)); + + priv->notification->dismissed.push_back(callback_data); + return callback_data->GetToken(); +} + +// TODO: refactor `Remove{Activated,Failed,Dismissed}` methods into one to deduplicate code +void winrt_windows_ui_notifications_toast_notification_RemoveActivated(winrtWindowsUINotificationsToastNotification* self, winrtEventToken* token) +{ + g_return_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (self)); + + winrtWindowsUINotificationsToastNotificationPrivate* priv = WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFICATION_GET_PRIVATE(self); + + erase_if(priv->notification->activated, [&](const auto& callback) { + if (winrt_event_token_get_value(token) == winrt_event_token_get_value(callback->GetToken())) + { + if (winrt_event_token_operator_bool(callback->GetToken())) + { + priv->notification->data.Activated(*winrt_event_token_get_internal(callback->GetToken())); + } + return true; + } + return false; + }); +} + +void winrt_windows_ui_notifications_toast_notification_RemoveFailed(winrtWindowsUINotificationsToastNotification* self, winrtEventToken* token) +{ + g_return_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (self)); + + winrtWindowsUINotificationsToastNotificationPrivate* priv = WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFICATION_GET_PRIVATE(self); + + erase_if(priv->notification->failed, [&](const auto& callback) { + if (winrt_event_token_get_value(token) == winrt_event_token_get_value(callback->GetToken())) + { + if (winrt_event_token_operator_bool(callback->GetToken())) + { + priv->notification->data.Failed(*winrt_event_token_get_internal(callback->GetToken())); + } + return true; + } + return false; + }); +} + +void winrt_windows_ui_notifications_toast_notification_RemoveDismissed(winrtWindowsUINotificationsToastNotification* self, winrtEventToken* token) +{ + g_return_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (self)); + + winrtWindowsUINotificationsToastNotificationPrivate* priv = WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFICATION_GET_PRIVATE(self); + + erase_if(priv->notification->dismissed, [&](const auto& callback) { + if (winrt_event_token_get_value(token) == winrt_event_token_get_value(callback->GetToken())) + { + if (winrt_event_token_operator_bool(callback->GetToken())) + { + priv->notification->data.Dismissed(*winrt_event_token_get_internal(callback->GetToken())); + } + return true; + } + return false; + }); +} diff --git a/plugins/windows-notification/api/src/gobject/winrt-toast-notifier.cpp b/plugins/windows-notification/api/src/gobject/winrt-toast-notifier.cpp new file mode 100644 index 00000000..e32e3fc6 --- /dev/null +++ b/plugins/windows-notification/api/src/gobject/winrt-toast-notifier.cpp @@ -0,0 +1,108 @@ +#include +#include +#include + +#include "winrt-toast-notifier-private.h" +#include "winrt-toast-notification-private.h" +#include "converter.hpp" + +#define WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFIER_GET_PRIVATE(obj) \ + ((winrtWindowsUINotificationsToastNotifierPrivate*) winrt_windows_ui_notifications_toast_notifier_get_instance_private ((winrtWindowsUINotificationsToastNotifier*) (obj))) + +typedef struct +{ + winrt::Windows::UI::Notifications::ToastNotifier data; +} _winrtWindowsUINotificationsToastNotifierPrivate; + +typedef struct +{ + _winrtWindowsUINotificationsToastNotifierPrivate* notifier; +} winrtWindowsUINotificationsToastNotifierPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (winrtWindowsUINotificationsToastNotifier, winrt_windows_ui_notifications_toast_notifier, G_TYPE_OBJECT) + +static void winrt_windows_ui_notifications_toast_notifier_finalize(GObject* self) +{ + winrtWindowsUINotificationsToastNotifierPrivate* priv = WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFIER_GET_PRIVATE (self); + + delete priv->notifier; + + G_OBJECT_CLASS(winrt_windows_ui_notifications_toast_notifier_parent_class)->dispose(self); +} + +static void winrt_windows_ui_notifications_toast_notifier_class_init (winrtWindowsUINotificationsToastNotifierClass* klass) +{ + GObjectClass* gobject_class = G_OBJECT_CLASS(klass); + + gobject_class->finalize = winrt_windows_ui_notifications_toast_notifier_finalize; +} + +static void winrt_windows_ui_notifications_toast_notifier_init (winrtWindowsUINotificationsToastNotifier */*self*/) +{ +} + +/*< private > + * winrt_windows_ui_notifications_toast_notifier_get_internal: + * @self: a #winrtWindowsUINotificationsToastNotifier + * + * Retrieves the `winrt::Windows::UI::Notifications::ToastNotifier` object used by @self. + * + * Returns: (transfer none): a pointer to the internal toast notification instance + */ +winrt::Windows::UI::Notifications::ToastNotifier* winrt_windows_ui_notifications_toast_notifier_get_internal(winrtWindowsUINotificationsToastNotifier *self) +{ + winrtWindowsUINotificationsToastNotifierPrivate *priv = WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFIER_GET_PRIVATE (self); + + return &priv->notifier->data; +} + +/*< private > + * winrt_windows_ui_notifications_toast_notifier_set_internal: + * @self: a #winrtWindowsUINotificationsToastNotifier + * @notification: a `winrt::Windows::UI::Notifications::ToastNotifier` instance + * + * Sets the internal database instance wrapped by @self, clearing + * any existing instance if needed. + */ +void winrt_windows_ui_notifications_toast_notifier_set_internal(winrtWindowsUINotificationsToastNotifier* self, winrt::Windows::UI::Notifications::ToastNotifier notifier) +{ + winrtWindowsUINotificationsToastNotifierPrivate* priv = WINRT_WINDOWS_UI_NOTIFICATION_TOAST_NOTIFIER_GET_PRIVATE(self); + + delete priv->notifier; + + priv->notifier = new _winrtWindowsUINotificationsToastNotifierPrivate { notifier }; +} + +/** + * winrt_windows_ui_notifications_toast_notifier_new: + * @doc: the document to be shown + * + * Creates a new toast notifier instance with its aumid set + * + * Returns: (transfer full): the newly created #winrtWindowsUINotificationsToastNotifier instance + */ +winrtWindowsUINotificationsToastNotifier* winrt_windows_ui_notifications_toast_notifier_new(const gchar* aumid) +{ + g_return_val_if_fail (aumid != NULL, NULL); + + auto ret = static_cast(g_object_new (WINRT_TYPE_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFIER, NULL)); + auto notifier = winrt::Windows::UI::Notifications::ToastNotificationManager::CreateToastNotifier(sview_to_wstr(aumid)); + winrt_windows_ui_notifications_toast_notifier_set_internal(ret, notifier); + return ret; +} + +void winrt_windows_ui_notifications_toast_notifier_Show(winrtWindowsUINotificationsToastNotifier* self, winrtWindowsUINotificationsToastNotification* toast_notification) +{ + g_return_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFIER (self)); + g_return_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (toast_notification)); + + winrt_windows_ui_notifications_toast_notifier_get_internal(self)->Show(*winrt_windows_ui_notifications_toast_notification_get_internal(toast_notification)); +} + +void winrt_windows_ui_notifications_toast_notifier_Hide(winrtWindowsUINotificationsToastNotifier* self, winrtWindowsUINotificationsToastNotification* toast_notification) +{ + g_return_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFIER (self)); + g_return_if_fail (WINRT_IS_WINDOWS_UI_NOTIFICATIONS_TOAST_NOTIFICATION (toast_notification)); + + winrt_windows_ui_notifications_toast_notifier_get_internal(self)->Hide(*winrt_windows_ui_notifications_toast_notification_get_internal(toast_notification)); +} diff --git a/plugins/windows-notification/api/src/gobject/winrt.cpp b/plugins/windows-notification/api/src/gobject/winrt.cpp new file mode 100644 index 00000000..31390250 --- /dev/null +++ b/plugins/windows-notification/api/src/gobject/winrt.cpp @@ -0,0 +1,35 @@ + +#include "gobject/winrt-private.h" +#include "converter.hpp" +#include "ginvoke.hpp" + +#include + +static void ImplInitApartment() +{ + const auto res = ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + if (FAILED(res)) + { + if (res == RPC_E_CHANGED_MODE) // seems harmless + g_info("attempted to change COM apartment mode of thread %" PRIu32, + std::uint32_t{::GetCurrentThreadId()}); + else + winrt::throw_hresult(res); + } +} + +gboolean winrt_InitApartment() noexcept +{ + return g_try_invoke0(ImplInitApartment).has_value(); +} + +static char* ImplGetTemplateContent(winrtWindowsUINotificationsToastTemplateType type) +{ + using namespace winrt::Windows::UI::Notifications; + return wsview_to_char(ToastNotificationManager::GetTemplateContent(static_cast(type)).GetXml()); +} + +char* winrt_windows_ui_notifications_toast_notification_manager_GetTemplateContent(winrtWindowsUINotificationsToastTemplateType type) noexcept +{ + return g_try_invoke(ImplGetTemplateContent, type).value_or(nullptr); +} diff --git a/plugins/windows-notification/api/src/shortcutcreator.cpp b/plugins/windows-notification/api/src/shortcutcreator.cpp new file mode 100644 index 00000000..e7ccfc11 --- /dev/null +++ b/plugins/windows-notification/api/src/shortcutcreator.cpp @@ -0,0 +1,130 @@ + +#include "shortcutcreator.h" +#include "win32.hpp" +#include "converter.hpp" +#include "ginvoke.hpp" +#include "dyn_mod.hpp" + +#include // COM stuff +#include // IShellLink +#include // InitPropVariantFromString +#include // PKEY_AppUserModel_ID +#include // At least one COM header must have been previously +// included, for `winrt::create_instance` to work with the `GUID` type. + +#include + +namespace dyn +{ + // PropVariantToString is a pain to use, and + // MinGW 6.0.0 doesn't have libpropsys.a in the first place; + // MinGW 9.0.0 doesn't have PropVariantToStringAlloc in its libpropsys.a. + // So... + constexpr auto PropVariantToStringAlloc = [](const auto &... arg) + { + static const auto &f = + dyn_load_symbol("propsys.dll", PropVariantToStringAlloc); + return f(arg...); + }; +} + +namespace { + +#define checked(func, args) \ + if (const auto hr = ((func)args); FAILED(hr)) \ + { \ + g_warning("%s%s failed: hresult 0x%08" PRIX32, \ + #func, #args, static_cast(hr)); \ + winrt::throw_hresult(hr); \ + } + +struct property +{ + property() noexcept : var{} {} + + explicit property(const std::wstring &value) + { + checked(::InitPropVariantFromString,(value.c_str(), &var)); + } + + ~property() + { + if (const auto hr = ::PropVariantClear(&var); FAILED(hr)) + g_critical("PropVariantClear failed: hresult 0x%08" PRIX32, + static_cast(hr)); + } + + auto str() const + { + wchar_t *str; + checked(dyn::PropVariantToStringAlloc,(var, &str)); + return std::unique_ptr + + { str, &::CoTaskMemFree }; + } + + operator const PROPVARIANT &() const noexcept { return var; } + operator PROPVARIANT *() noexcept { return &var; } + +private: + PROPVARIANT var; +}; + +void ImplEnsureAumiddedShortcutExists( + const std::string_view menu_rel_path, const std::string_view narrow_aumid) +{ + if (menu_rel_path.empty()) + throw std::runtime_error{"empty menu-relative shortcut path"}; + + const auto aumid = sview_to_wstr(narrow_aumid); + + const auto exe_path = GetExePath(); + const auto shortcut_path = GetEnv(L"APPDATA") + + LR"(\Microsoft\Windows\Start Menu\)" + + sview_to_wstr(menu_rel_path) + L".lnk"; + + const auto lnk = winrt::create_instance(CLSID_ShellLink); + const auto file = lnk.as(); + const auto store = lnk.as(); + + if (SUCCEEDED(file->Load(shortcut_path.c_str(), STGM_READWRITE))) + { + property aumid_prop; + checked(store->GetValue,(PKEY_AppUserModel_ID, aumid_prop)); + if (aumid_prop.str().get() != aumid) + checked(store->SetValue,(PKEY_AppUserModel_ID, property{aumid})); + + std::array targ_path; + checked(lnk->GetPath,(targ_path.data(), targ_path.size(), nullptr, 0)); + if (targ_path.data() != exe_path) + checked(lnk->SetPath,(exe_path.c_str())); + } + else + { + checked(store->SetValue,(PKEY_AppUserModel_ID, property{aumid})); + checked(lnk->SetPath,(exe_path.c_str())); + } + + checked(store->Commit,()); + + if (file->IsDirty() != S_FALSE) // not the same as `== S_OK` + { + constexpr auto set_file_as_current = TRUE; + checked(file->Save,(shortcut_path.c_str(), set_file_as_current)); + } +} + +#undef checked + +} // nameless namespace + + +extern "C" +{ + gboolean EnsureAumiddedShortcutExists(const gchar *const aumid) noexcept + { + return g_try_invoke( + ImplEnsureAumiddedShortcutExists, R"(Programs\Dino)", aumid) + .has_value(); + } +} diff --git a/plugins/windows-notification/api/src/win32.cpp b/plugins/windows-notification/api/src/win32.cpp new file mode 100644 index 00000000..02e2a47c --- /dev/null +++ b/plugins/windows-notification/api/src/win32.cpp @@ -0,0 +1,104 @@ +#include +#include + +#include "win32.hpp" +#include "converter.hpp" +#include "ginvoke.hpp" + +#include + +win32_error::win32_error() noexcept + : win32_error{::GetLastError()} +{} + +constexpr auto noncharacter = L'\uFFFF'; + +template +static std::wstring GetStringOfGuessableLength(const Oracle &take_a_guess) +{ + constexpr auto grow = [](const std::size_t s) { return s + s/2; }; + static_assert( + grow(InitialGuess) != InitialGuess, "imminent infinite loop"); + + std::wstring buf(InitialGuess, noncharacter); + auto maybe_len = take_a_guess(buf.data(), static_cast(buf.size())); + + if (not maybe_len) do + { + constexpr auto dw_max = std::size_t{std::numeric_limits::max()}; + if (buf.size() == dw_max) + throw std::runtime_error{"wat, string too long for DWORD?"}; + buf.resize(std::min(grow(buf.size()), dw_max)); + maybe_len = take_a_guess(buf.data(), static_cast(buf.size())); + } + while (not maybe_len); + + buf.resize(*maybe_len); + return buf; +} + +std::wstring GetExePath() +{ + const auto try_get_exe_path = []( + const auto buf, const auto bufsize) -> std::optional + { + constexpr HMODULE exe_module = nullptr; + ::SetLastError(0); // just in case + const auto res = ::GetModuleFileNameW(exe_module, buf, bufsize); + if (const auto e = ::GetLastError(); + e == ERROR_INSUFFICIENT_BUFFER or res == bufsize) + return {}; + else if (not e) + return res; + else + throw win32_error{e}; + }; + + return GetStringOfGuessableLength(try_get_exe_path); +} + +std::wstring GetEnv(const wchar_t *const variable_name) +{ + const auto bufsize = ::GetEnvironmentVariableW(variable_name, nullptr, 0); + if (not bufsize) + throw win32_error{}; + std::wstring buf(bufsize, noncharacter); + ::SetLastError(0); + const auto res = + ::GetEnvironmentVariableW(variable_name, buf.data(), bufsize); + if (const auto e = ::GetLastError()) + throw win32_error{e}; + if (not res or res >= bufsize) // not entirely sure this isn't just paranoia + throw std::runtime_error{"GetEnvironmentVariableW misbehaved"}; + buf.resize(res); + return buf; +} + + +static void ImplSetProcessAumid(const std::string_view aumid) +{ + winrt::check_hresult(::SetCurrentProcessExplicitAppUserModelID( + sview_to_wstr(aumid).c_str())); +} + +extern "C" +{ + // Not available in mingw headers, but linking works. + NTSTATUS NTAPI RtlGetVersion(PRTL_OSVERSIONINFOW); + + gboolean IsWindows10() noexcept + { + RTL_OSVERSIONINFOW rovi = {}; + rovi.dwOSVersionInfoSize = sizeof(rovi); + if (S_OK == RtlGetVersion(&rovi)) + { + return rovi.dwMajorVersion > 6; + } + return FALSE; + } + + gboolean SetProcessAumid(const gchar *const aumid) noexcept + { + return g_try_invoke(ImplSetProcessAumid, aumid).has_value(); + } +} diff --git a/plugins/windows-notification/src/toast_notification_builder.vala b/plugins/windows-notification/src/toast_notification_builder.vala new file mode 100644 index 00000000..c976c9bc --- /dev/null +++ b/plugins/windows-notification/src/toast_notification_builder.vala @@ -0,0 +1,240 @@ +using Dino.Entities; +using Dino.Plugins.WindowsNotification.Vapi; +using winrt.Windows.UI.Notifications; +using Dino.Plugins.WindowsNotification.Vapi.Win32Api; +using Xmpp; + +namespace Dino.Plugins.WindowsNotification { + private delegate void NodeFunction(StanzaNode node); + + public enum ActivationType { + Foreground, + Background + } + + public enum Scenario { + Basic, + IncomingCall + } + + private class Button { + public string title; + public string arguments; + public string imageUri; + public ActivationType activationType; + } + + public class ToastNotificationBuilder { + private static bool _supportsModernFeatures = IsWindows10(); + private Gee.List