From 6fd8f9a45c286b787aa251177e80f75ac85df7b7 Mon Sep 17 00:00:00 2001 From: Maxim Logaev Date: Mon, 18 Mar 2024 22:51:50 +0300 Subject: [PATCH] Added full Windows support (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Windows compatibility Tweaks * Add experimental windows installer This nsis script should create a windows installer. Although the installer worked for the first tests you should handle it with care and consider it highly experimental * Prepare signing Collected some infos regarding signing a windows build. * Revert "Prepare signing" I copied the files into the wrong folder… … it's late, sorry. This reverts commit 7d6b9e7f4cd066b4b59eddec09be3bcac5c6c656. * Prepare signing Collected some infos regarding signing a windows build. * Fix typo in Dino slogan * Add license to windows installer * Add startmenu folder with several items Added a startmenu folder with the following items: * Dino launcher * License * Link to Dino website * Uninstaller * Prevent duplicated DLLs * Add dino logo again The dino logo for the startmenu was accidentally no longer included since the last commit. * Simplify installer script The current build script already places the files in the right folder structure so the installer doesn't have to do it itself * Add german language. * Add option to install without OpenPGP plugin * Removed compenent section This section was only introduced to be able to disable the OpenPGP plugin as Dino often crashed on Windows if OpenPGP was not installed but the plugin enabled. This is no more necessary as the OpenPGP plugin is now disabled by default. * Remove installation type "OpenPGP" support This is no longer needed (see previous commit) but was forgotten to remove in the previous commit. * Add compression to achieve smaller installer size. * Add AppID (untested). * Fix syntax error for setting AppID. * Windows compatibility Tweaks * fix build on newest MSYS2 * Do not search for the built-in libraries when compiling with MINGW * Added _WIN32 define to VALAC on Windows * Add missing _WIN32 * Add support for OpenPGP on Windows * Use ShellExecute instead of AppInfo to open files on Windows * Use slight larger font on Windows so it matches Linux style Also fixes some fuzzy fonts. * Fixed some Windows not appearing when opening file * Set alternate file stream for downloaded files. * Added information and Dino icon to Windows executable * Set Windows executable version from PROJECT_VERSION * Add WIN32 fonts as a plugin * Every call to CoInitialize() must be balanced with a call to CoUninitialize() * Add --export-all-symbols to Windows compilation * Add implicit link directories to package HINT path on MingW Instead of blacklisting those libraries * Do not hardcode GPG path on Windows * Export all plugin symbols on Windows * Use Dino.Util.get_content_type also on preview * Allow 32-bit linking Win32 apis are __stdcall * Use last_index_of instead of index_of * Initial notification support * Refactor windows-notification plugin * Clean up * Use code from Dino.Ui.Util * Convert C code to Vala * Add callback support * Allow null image_path * Use dynamic linking instead of runtime loading Also made me notice that the signature of the function with the callback was wrong. Oops. * Added 32-bit wintoast linker library * Use VAPI and generate template in-app * Initial plugin using new notification provider * Add support for custom actions on notification * Add notification retraction * Use list with all notifications * Rename field * Fix muc invite and voide request not working * Do not use GLib to open links in messages Use ShellExecute * Add MIT licensed winrt headers * Initial code for using winrt headers * Initial callback support * Initial GObject wrapper for WinRT notifications Still missing a lot of stuff * Initial code to allow buttons and text * Use string_view * Increase ref on event token * Add toastnotifier * Fix string conversion * Actions can stack * Remove unity compilation unit * No need to enable coroutines * Fields must be created in the private struct Also change unordered_map to list, we do not need hashing and stuff. * Add failed and dimissed actions * Cleanup dismissed actions on toast notification finalizer * Add template type enum * Rename enums to better match what Vala expects * Rename plugin vala file * Add template getter * Initial experiments with notification XML building * Anitial builder * Initial notification provider using WinRT Crashes when activating actions, might be related to threads. * Delegate `activate_action` to UI thread * Fixed crash with multiple notifications Sometimes an invalid function pointer was called with an invalid context * Add comment to builder * Use async * Use g_new0 and g_free to generate raw strings * Valac think that getters are always owned by the struct * introduce try_invoke -- a logging exception catcher * stop exceptions from crossing ABI boundary in a few places * mark exception-safe C entry points as such * clarify some entry points' names * make GetCurrentModulePath and GetShortcutPath throw win32 errors * clarify GetCurrentModulePath's name * generalize GetShortcutPath into GetEnv * make GetEnv more robust and not limit length of variables * change some local functions' signatures * constify all the things * rewrite shortcut management code with RAII, error logging and exceptions It actually works now. * add restoration of shortcut's target path * switch to runtime loading of PropVariantToStringAlloc Now it really should work. * Add ginvoke to CMakeLists * Removed unused library on linker It is loaded dynamically * Add README.md to Windows notification plugin * Fix notifications not hiding * unimplement accidentally implemented wide string overloads of describe_argument * work around GetEnvironmentVariable not resetting last error * handle exe paths longer than 259 chars * move some whitespace around * use lower-case 0x prefix for hresult code formatting everywhere * remove an unused include * make meta-error messages more precise * handle empty hresult_error message specially * handle theoretical future failures of wsview_to_char * fix UB in glib::describe_arguments called with no arguments Makes failure logging of nullary invokables non-crashy. * make glib::impl::varstring less explosive * fiddle with punctuation * add nullary version of g_try_invoke macro * generalize glib::try_invoke to any return-by-value type and void * protect GetTemplateContent callers from exceptions * rewrite InitApartment and protect callers from (the rest of the) exceptions Initializing COM by calling `winrt::init_apartment()` would always cause stack unwinding *in practice*, which is suboptimal at best, and even using `apartment_type::single_threaded` still would require exception filtering *just in case*. * handle empty menu-relative shortcut paths * move module loading functions out of shortcutcreator.cpp * work around a (pedantic) format specifier warning * silence enum stringification warnings by first casting to underlying types * fix / work around uninitialized fields warnings * don't use FALSE as a null pointer constant * replace C-style concurrent initialization of statics C++ statics are thread-safe as is and are usually implemented more efficiently. Besides, `volatile` is likely misused here anyway. * reflow/respace * stop checking for empty AUMIDs The downstream code handles them just fine. * log SetCurrentProcessExplicitAppUserModelID errors * remove the no-longer-needed -municode compile option * replace lists with vectors * init `Callback` completely always The `token` pointer was left dangerously uninitialized after construction. * comment out unused arguments [-Wunused-parameter] * Add support for adaptive Windows 10 notifications * Add support for inline images to notification * Allow null header, body, applogo, and image on notification builder * DelegateToUi must be an owned function * Prefer primary DirectSound device on Windows It automatically selects the default device for use, there is no book keeping necessary and things just work The primary DirectSound device has a (NULL) guid, making it wasy to be found. * Do not allow selection of WASAPI devices Dino would have to resample it own audio, do more book keeping and somehow find out manually which is the default device. * Add initial call notifications * Use correct generic type for ArrayList Nullable crashes Dino * Allow devices with properties and use has_classes * Remove YoloRT from tree * Build YoloRT on project build * Do not generate WinRT headers, just download them on build * Fix compilation on gcc 11 * define _POSIX_C_SOURCE=1 on windows Fixes "undefined reference to `localtime_r`" in, e.g., Vala's GLib.Time.local when building on mingw-w64. * fix call notifications buttons not working * no need to ignore wasapi * Ignore wasapi devices as they do not work well yet * Removed version from Dino executable We need a better way to get the version number * Automatically set PANGOCAIRO_BACKEND to fontconfig on win32 * Fixed using GTK3 instead of GTK4 * Check YoloRT checksum before building * Fix GPGME * Added build script for windows Signed-off-by: Maxim Logaev * Added README-WIN64.md Signed-off-by: Maxim Logaev * Fixed dist-install dir Signed-off-by: Maxim Logaev * Removed unnecessary installer files Signed-off-by: Maxim Logaev * Added build-installer target to build-win64.sh Signed-off-by: Maxim Logaev * Fixed build dependencies Signed-off-by: Maxim Logaev * Move download yolort headers logic into prepare stage, delete yolort download script * Added CI for MSYS2 (MINGW64) (#2) - Use quotes in windows build script; - Added missing gstreamer, webrtc-audio-processing and git; - Added CI for Windows. --------- Signed-off-by: Maxim Logaev --------- Signed-off-by: Maxim Logaev Co-authored-by: LAGonauta Co-authored-by: Martin Dosch Co-authored-by: Martin Dosch Co-authored-by: mjk Co-authored-by: Daniel Reuther Co-authored-by: Felipe Co-authored-by: Psayker --- .github/workflows/build-win64.yml | 26 ++ .gitignore | 3 + CMakeLists.txt | 8 + README-WIN64.md | 56 +++ build-win64.sh | 182 +++++++++ cmake/PkgConfigWithFallback.cmake | 7 +- .../PkgConfigWithFallbackOnConfigScript.cmake | 7 +- libdino/src/service/avatar_manager.vala | 18 + libdino/src/service/file_manager.vala | 11 +- libdino/src/service/util.vala | 48 ++- main/CMakeLists.txt | 8 +- main/dino-info.rc | 21 + main/dino.ico | Bin 0 -> 100395 bytes main/src/main.vala | 6 + .../file_widget.vala | 2 +- .../message_widget.vala | 10 +- main/src/ui/file_send_overlay.vala | 2 +- .../manage_accounts/add_account_dialog.vala | 2 +- plugins/CMakeLists.txt | 4 + plugins/openpgp/src/gpgme_helper.vala | 7 + plugins/openpgp/vapi/gpgme.vapi | 3 + plugins/rtp/src/plugin.vala | 2 + plugins/win32-fonts/CMakeLists.txt | 43 ++ plugins/win32-fonts/data/larger.css | 3 + plugins/win32-fonts/src/plugin.vala | 16 + plugins/win32-fonts/src/register_plugin.vala | 3 + plugins/windows-notification/.gitignore | 1 + plugins/windows-notification/CMakeLists.txt | 77 ++++ plugins/windows-notification/README.md | 10 + .../api/include/converter.hpp | 8 + .../api/include/dyn_mod.hpp | 29 ++ .../api/include/ginvoke.hpp | 172 ++++++++ .../api/include/gobject/winrt-enums.h | 61 +++ .../gobject/winrt-event-token-private.h | 12 + .../api/include/gobject/winrt-event-token.h | 36 ++ .../api/include/gobject/winrt-glib-types.h | 7 + .../api/include/gobject/winrt-glib.h | 18 + .../api/include/gobject/winrt-headers.h | 9 + .../api/include/gobject/winrt-private.h | 8 + .../winrt-toast-notification-private.h | 12 + .../gobject/winrt-toast-notification.h | 58 +++ .../gobject/winrt-toast-notifier-private.h | 12 + .../include/gobject/winrt-toast-notifier.h | 39 ++ .../api/include/gobject/winrt.h | 24 ++ .../api/include/hexify.hpp | 12 + .../api/include/make_array.hpp | 15 + .../api/include/notificationhandler.hpp | 18 + .../api/include/overload.hpp | 10 + .../api/include/shortcutcreator.h | 16 + .../windows-notification/api/include/win32.h | 1 + .../api/include/win32.hpp | 47 +++ .../api/src/converter.cpp | 29 ++ .../windows-notification/api/src/dyn_mod.cpp | 32 ++ .../windows-notification/api/src/ginvoke.cpp | 45 ++ .../api/src/gobject/winrt-enums.cpp | 31 ++ .../api/src/gobject/winrt-event-token.cpp | 76 ++++ .../src/gobject/winrt-toast-notification.cpp | 383 ++++++++++++++++++ .../api/src/gobject/winrt-toast-notifier.cpp | 108 +++++ .../api/src/gobject/winrt.cpp | 35 ++ .../api/src/shortcutcreator.cpp | 130 ++++++ .../windows-notification/api/src/win32.cpp | 104 +++++ .../src/toast_notification_builder.vala | 240 +++++++++++ .../src/win_notification_provider.vala | 328 +++++++++++++++ .../src/windows_notifications_plugin.vala | 35 ++ ...windows_notifications_register_plugin.vala | 3 + plugins/windows-notification/vapi/enums.vapi | 9 + .../vapi/shortcutcreator.vapi | 5 + plugins/windows-notification/vapi/win32.vapi | 9 + plugins/windows-notification/vapi/winrt.vapi | 13 + .../vapi/winrt_windows_ui_notifications.vapi | 68 ++++ windows-installer/LICENSE_SHORT | 15 + windows-installer/dino.nsi | 68 ++++ windows-installer/english.nsh | 3 + 73 files changed, 2969 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/build-win64.yml create mode 100644 README-WIN64.md create mode 100644 build-win64.sh create mode 100644 main/dino-info.rc create mode 100644 main/dino.ico create mode 100644 plugins/win32-fonts/CMakeLists.txt create mode 100644 plugins/win32-fonts/data/larger.css create mode 100644 plugins/win32-fonts/src/plugin.vala create mode 100644 plugins/win32-fonts/src/register_plugin.vala create mode 100644 plugins/windows-notification/.gitignore create mode 100644 plugins/windows-notification/CMakeLists.txt create mode 100644 plugins/windows-notification/README.md create mode 100644 plugins/windows-notification/api/include/converter.hpp create mode 100644 plugins/windows-notification/api/include/dyn_mod.hpp create mode 100644 plugins/windows-notification/api/include/ginvoke.hpp create mode 100644 plugins/windows-notification/api/include/gobject/winrt-enums.h create mode 100644 plugins/windows-notification/api/include/gobject/winrt-event-token-private.h create mode 100644 plugins/windows-notification/api/include/gobject/winrt-event-token.h create mode 100644 plugins/windows-notification/api/include/gobject/winrt-glib-types.h create mode 100644 plugins/windows-notification/api/include/gobject/winrt-glib.h create mode 100644 plugins/windows-notification/api/include/gobject/winrt-headers.h create mode 100644 plugins/windows-notification/api/include/gobject/winrt-private.h create mode 100644 plugins/windows-notification/api/include/gobject/winrt-toast-notification-private.h create mode 100644 plugins/windows-notification/api/include/gobject/winrt-toast-notification.h create mode 100644 plugins/windows-notification/api/include/gobject/winrt-toast-notifier-private.h create mode 100644 plugins/windows-notification/api/include/gobject/winrt-toast-notifier.h create mode 100644 plugins/windows-notification/api/include/gobject/winrt.h create mode 100644 plugins/windows-notification/api/include/hexify.hpp create mode 100644 plugins/windows-notification/api/include/make_array.hpp create mode 100644 plugins/windows-notification/api/include/notificationhandler.hpp create mode 100644 plugins/windows-notification/api/include/overload.hpp create mode 100644 plugins/windows-notification/api/include/shortcutcreator.h create mode 100644 plugins/windows-notification/api/include/win32.h create mode 100644 plugins/windows-notification/api/include/win32.hpp create mode 100644 plugins/windows-notification/api/src/converter.cpp create mode 100644 plugins/windows-notification/api/src/dyn_mod.cpp create mode 100644 plugins/windows-notification/api/src/ginvoke.cpp create mode 100644 plugins/windows-notification/api/src/gobject/winrt-enums.cpp create mode 100644 plugins/windows-notification/api/src/gobject/winrt-event-token.cpp create mode 100644 plugins/windows-notification/api/src/gobject/winrt-toast-notification.cpp create mode 100644 plugins/windows-notification/api/src/gobject/winrt-toast-notifier.cpp create mode 100644 plugins/windows-notification/api/src/gobject/winrt.cpp create mode 100644 plugins/windows-notification/api/src/shortcutcreator.cpp create mode 100644 plugins/windows-notification/api/src/win32.cpp create mode 100644 plugins/windows-notification/src/toast_notification_builder.vala create mode 100644 plugins/windows-notification/src/win_notification_provider.vala create mode 100644 plugins/windows-notification/src/windows_notifications_plugin.vala create mode 100644 plugins/windows-notification/src/windows_notifications_register_plugin.vala create mode 100644 plugins/windows-notification/vapi/enums.vapi create mode 100644 plugins/windows-notification/vapi/shortcutcreator.vapi create mode 100644 plugins/windows-notification/vapi/win32.vapi create mode 100644 plugins/windows-notification/vapi/winrt.vapi create mode 100644 plugins/windows-notification/vapi/winrt_windows_ui_notifications.vapi create mode 100644 windows-installer/LICENSE_SHORT create mode 100644 windows-installer/dino.nsi create mode 100644 windows-installer/english.nsh 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 0000000000000000000000000000000000000000..898e438766dcc25f69da186b00f74d73209c448c GIT binary patch literal 100395 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�)$hi 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 literal 0 HcmV?d00001 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