Added full Windows support (#8)

* 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 7d6b9e7f4c.

* 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 <maxlogaev@proton.me>

* Added README-WIN64.md

Signed-off-by: Maxim Logaev <maxlogaev@proton.me>

* Fixed dist-install dir

Signed-off-by: Maxim Logaev <maxlogaev@proton.me>

* Removed unnecessary installer files

Signed-off-by: Maxim Logaev <maxlogaev@proton.me>

* Added build-installer target to build-win64.sh

Signed-off-by: Maxim Logaev <maxlogaev@proton.me>

* Fixed build dependencies

Signed-off-by: Maxim Logaev <maxlogaev@proton.me>

* 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 <maxlogaev@proton.me>

---------

Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
Co-authored-by: LAGonauta <lagonauta@gmail.com>
Co-authored-by: Martin Dosch <spam@mdosch.de>
Co-authored-by: Martin Dosch <martin@mdosch.de>
Co-authored-by: mjk <yuubi-san@users.noreply.github.com>
Co-authored-by: Daniel Reuther <daniel.reuther@liferay.com>
Co-authored-by: Felipe <LAGonauta@users.noreply.github.com>
Co-authored-by: Psayker <kirill970528@yandex.ru>
This commit is contained in:
Maxim Logaev 2024-03-18 22:51:50 +03:00 committed by GitHub
parent bf9f401743
commit 6fd8f9a45c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
73 changed files with 2969 additions and 10 deletions

26
.github/workflows/build-win64.yml vendored Normal file
View file

@ -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

3
.gitignore vendored
View file

@ -6,3 +6,6 @@ Makefile
.idea .idea
.sqlite3 .sqlite3
gschemas.compiled gschemas.compiled
windows-installer/win64-dist/
*.exe
*.dll

View file

@ -12,6 +12,9 @@ endif ()
# Prepare Plugins # Prepare Plugins
set(DEFAULT_PLUGINS omemo;openpgp;http-files;ice;rtp) 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}) foreach (plugin ${DEFAULT_PLUGINS})
if ("$CACHE{DINO_PLUGIN_ENABLED_${plugin}}" STREQUAL "") if ("$CACHE{DINO_PLUGIN_ENABLED_${plugin}}" STREQUAL "")
if (NOT DEFINED DINO_PLUGIN_ENABLED_${plugin}}) if (NOT DEFINED DINO_PLUGIN_ENABLED_${plugin}})
@ -176,6 +179,11 @@ if (NOT NO_DEBUG)
set(CMAKE_VALA_FLAGS "${CMAKE_VALA_FLAGS} -g") set(CMAKE_VALA_FLAGS "${CMAKE_VALA_FLAGS} -g")
endif (NOT NO_DEBUG) 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_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

56
README-WIN64.md Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.

182
build-win64.sh Normal file
View file

@ -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

View file

@ -13,11 +13,16 @@ function(find_pkg_config_with_fallback name)
# Found via pkg-config, using its result values # Found via pkg-config, using its result values
set(${name}_FOUND ${${name}_PKG_CONFIG_FOUND}) 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 # Try to find real file name of libraries
foreach(lib ${${name}_PKG_CONFIG_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) mark_as_advanced(${name}_${lib}_LIBRARY)
if(NOT ${name}_${lib}_LIBRARY) if(NOT ${name}_${lib}_LIBRARY)
message(${name} ": " ${lib} " library not found")
unset(${name}_FOUND) unset(${name}_FOUND)
endif(NOT ${name}_${lib}_LIBRARY) endif(NOT ${name}_${lib}_LIBRARY)
endforeach(lib) endforeach(lib)

View file

@ -13,11 +13,16 @@ function(find_pkg_config_with_fallback_on_config_script name)
# Found via pkg-config, using it's result values # Found via pkg-config, using it's result values
set(${name}_FOUND ${${name}_PKG_CONFIG_FOUND}) 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 # Try to find real file name of libraries
foreach(lib ${${name}_PKG_CONFIG_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) mark_as_advanced(${name}_${lib}_LIBRARY)
if(NOT ${name}_${lib}_LIBRARY) if(NOT ${name}_${lib}_LIBRARY)
message(${name} ": " ${lib} " library not found")
unset(${name}_FOUND) unset(${name}_FOUND)
endif(NOT ${name}_${lib}_LIBRARY) endif(NOT ${name}_${lib}_LIBRARY)
endforeach(lib) endforeach(lib)

View file

@ -304,6 +304,24 @@ public class AvatarManager : StreamInteractionModule, Object {
return null; 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);
}
} }
} }

View file

@ -63,7 +63,7 @@ public class FileManager : StreamInteractionModule, Object {
try { try {
FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE); FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE);
file_transfer.file_name = file_info.get_display_name(); 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.size = (int)file_info.get_size();
file_transfer.input_stream = yield file.read_async(); 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(); file_transfer.input_stream = yield file.read_async();
FileInfo file_info = file_transfer.get_file().query_info("*", FileQueryInfoFlags.NONE); 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; 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) { } catch (Error e) {
warning("Error downloading file: %s", e.message); warning("Error downloading file: %s", e.message);
file_transfer.state = FileTransfer.State.FAILED; file_transfer.state = FileTransfer.State.FAILED;

View file

@ -4,6 +4,25 @@ using Qlite;
namespace Dino { namespace Dino {
public class Util { 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) { public static Message.Type get_message_type_for_conversation(Conversation conversation) {
switch (conversation.type_) { switch (conversation.type_) {
case Conversation.Type.CHAT: case Conversation.Type.CHAT:
@ -29,6 +48,33 @@ public class Util {
assert_not_reached(); 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();
}
}
} }

View file

@ -256,7 +256,13 @@ OPTIONS
) )
add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DG_LOG_DOMAIN="dino") add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DG_LOG_DOMAIN="dino")
if(WIN32)
add_link_options("-Wl,--export-all-symbols")
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> --use-temp-file -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
add_executable(dino ${MAIN_VALA_C} ${MAIN_GRESOURCES_TARGET} dino-info.rc)
else(WIN32)
add_executable(dino ${MAIN_VALA_C} ${MAIN_GRESOURCES_TARGET}) add_executable(dino ${MAIN_VALA_C} ${MAIN_GRESOURCES_TARGET})
endif(WIN32)
add_dependencies(dino ${GETTEXT_PACKAGE}-translations) add_dependencies(dino ${GETTEXT_PACKAGE}-translations)
target_include_directories(dino PRIVATE src) target_include_directories(dino PRIVATE src)
target_link_libraries(dino libdino ${MAIN_PACKAGES}) target_link_libraries(dino libdino ${MAIN_PACKAGES})

21
main/dino-info.rc Normal file
View file

@ -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"

BIN
main/dino.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View file

@ -9,6 +9,12 @@ namespace Dino {
void main(string[] args) { void main(string[] args) {
try{ 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; string? exec_path = args.length > 0 ? args[0] : null;
SearchPathGenerator search_path_generator = new SearchPathGenerator(exec_path); SearchPathGenerator search_path_generator = new SearchPathGenerator(exec_path);
Intl.textdomain(GETTEXT_PACKAGE); Intl.textdomain(GETTEXT_PACKAGE);

View file

@ -176,7 +176,7 @@ public class FileWidgetController : Object {
private void open_file() { private void open_file() {
try{ 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) { } catch (Error err) {
warning("Failed to open %s - %s", file_transfer.get_file().get_uri(), err.message); warning("Failed to open %s - %s", file_transfer.get_file().get_uri(), err.message);
} }

View file

@ -242,7 +242,15 @@ public class MessageMetaItem : ContentMetaItem {
public static bool on_label_activate_link(string uri) { public static bool on_label_activate_link(string uri) {
// Always handle xmpp URIs with Dino // 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); File file = File.new_for_uri(uri);
Dino.Application.get_default().open(new File[]{file}, ""); Dino.Application.get_default().open(new File[]{file}, "");
return true; return true;

View file

@ -57,7 +57,7 @@ public class FileSendOverlay {
private async void load_file_widget(File file, FileInfo file_info) { private async void load_file_widget(File file, FileInfo file_info) {
string file_name = file_info.get_display_name(); 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; bool is_image = false;

View file

@ -373,7 +373,7 @@ public class AddAccountDialog : Gtk.Dialog {
// Button is opening a registration website // Button is opening a registration website
if (form.oob != null) { if (form.oob != null) {
try { try {
AppInfo.launch_default_for_uri(form.oob, null); Dino.Util.launch_default_for_uri(form.oob);
} catch (Error e) { } } catch (Error e) { }
show_sign_in_jid(); show_sign_in_jid();
return; return;

View file

@ -1,3 +1,7 @@
if(WIN32)
add_link_options("-Wl,--export-all-symbols")
endif(WIN32)
foreach(plugin ${PLUGINS}) foreach(plugin ${PLUGINS})
add_subdirectory(${plugin}) add_subdirectory(${plugin})
endforeach(plugin) endforeach(plugin)

View file

@ -177,6 +177,13 @@ private static uint8[] get_uint8_from_data(Data data) {
private static void initialize() { private static void initialize() {
if (!initialized) { 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(); check_version();
initialized = true; initialized = true;
} }

View file

@ -665,6 +665,9 @@ namespace GPG {
[CCode (cname = "gpgme_strerror")] [CCode (cname = "gpgme_strerror")]
public unowned string strerror(GPGError.Error err); 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 { private void throw_if_error(GPGError.Error error) throws GLib.Error {
if (error.code != GPGError.ErrorCode.NO_ERROR) { if (error.code != GPGError.ErrorCode.NO_ERROR) {
throw new GLib.Error(-1, error.code, "%s", error.to_string()); throw new GLib.Error(-1, error.code, "%s", error.to_string());

View file

@ -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.get_bus().add_watch(Priority.DEFAULT, on_device_monitor_message);
device_monitor.start(); device_monitor.start();
foreach (Gst.Device device in device_monitor.get_devices()) { 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.has_name("pipewire-proplist") && device.has_classes("Audio")) continue;
if (device.properties.get_string("device.class") == "monitor") continue; if (device.properties.get_string("device.class") == "monitor") continue;
if (devices.any_match((it) => it.matches(device))) continue; if (devices.any_match((it) => it.matches(device))) continue;

View file

@ -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})

View file

@ -0,0 +1,3 @@
* {
font-size: 1.125rem;
}

View file

@ -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() { }
}
}

View file

@ -0,0 +1,3 @@
public Type register_plugin(Module module) {
return typeof (Dino.Plugins.Win32Fonts.Plugin);
}

View file

@ -0,0 +1 @@
/yolort

View file

@ -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 $<$<COMPILE_LANGUAGE:CXX>:-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})

View file

@ -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

View file

@ -0,0 +1,8 @@
#pragma once
#include <string>
#include <string_view>
#include <glib.h>
std::wstring sview_to_wstr(const std::string_view str);
gchar* wsview_to_char(const std::wstring_view wstr);

View file

@ -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<typename T>
inline T &load_symbol(
const wchar_t *const mod_path, const char *const mod_dbgnym,
const char *const symbol)
{
return reinterpret_cast<T &>(load_symbol(mod_path, mod_dbgnym, symbol));
}
}
#define dyn_load_symbol_ns(mod_name, namespace, symbol) \
::dyn_mod::load_symbol<decltype(namespace::symbol)>( \
L ## mod_name, mod_name, #symbol)
#define dyn_load_symbol(mod_name, symbol) dyn_load_symbol_ns(mod_name, ,symbol)
#endif

View file

@ -0,0 +1,172 @@
#ifndef GINVOKE_HPP
#define GINVOKE_HPP
#include <type_traits>
#include <string>
#include <string_view>
#include <sstream>
#include <iomanip>
#include <utility>
#include <optional>
#include <variant>
#include <iterator>
#include <functional>
#include <exception>
#include <cstdint>
#include <cinttypes>
#include <glib.h>
#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<std::string, static_c_str>;
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<const varstring_t &>(*this));
}
};
struct hresult
{
std::int32_t code;
varstring message;
};
std::optional<hresult> get_if_hresult_error(std::exception_ptr) noexcept;
}
template<typename OStream, typename T,
std::enable_if_t<!std::is_enum_v<T>,int> = 0>
inline auto &describe_argument(OStream &s, const T &a)
{ return s << a; }
template<typename OStream, typename T,
std::enable_if_t< std::is_enum_v<T>,int> = 0>
inline auto &describe_argument(OStream &s, const T &a)
{ return s << static_cast<std::underlying_type_t<T>>(a); }
template<typename OStream>
inline auto &describe_argument(OStream &s, std::string_view const a)
{ return s << std::quoted(a); }
template<typename OStream>
inline auto &describe_argument(OStream &s, const std::string & a)
{ return s << std::quoted(a); }
template<typename OStream>
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<typename OStream>
inline auto &describe_argument(OStream &s, std::wstring_view const a) = delete;
template<typename OStream>
inline auto &describe_argument(OStream &s, const std::wstring & a) = delete;
template<typename OStream>
inline auto &describe_argument(OStream &s, const wchar_t * const a) = delete;
inline impl::varstring describe_arguments() noexcept { return {""}; }
template<typename... Arg>
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 {"<failed to stringify arguments>"};
}
#define FORMAT "%s(%s) failed: %s"
template<typename... Arg>
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<typename... Arg>
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<typename Invokable, typename... Arg>
inline auto invoke(Invokable &&i, const Arg &... a)
{
using R = decltype(std::invoke(std::forward<Invokable>(i), a...));
if constexpr (std::is_void_v<R>)
{
std::invoke(std::forward<Invokable>(i), a...);
return regular_void{};
}
else
return std::invoke(std::forward<Invokable>(i), a...);
}
template<typename Invokable, typename... Arg>
inline auto try_invoke(
const char *func_name, Invokable &&i, const Arg &... a) noexcept
-> std::optional<decltype(invoke(std::forward<Invokable>(i), a...))>
try
{
return invoke(std::forward<Invokable>(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<std::uint32_t>(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

View file

@ -0,0 +1,61 @@
#ifndef __WINRT_ENUMS_H__
#define __WINRT_ENUMS_H__
#include <glib-object.h>
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__ */

View file

@ -0,0 +1,12 @@
#ifndef __WINRT_GLIB_EVENTTOKEN_PRIVATE_H__
#define __WINRT_GLIB_EVENTTOKEN_PRIVATE_H__
#include <glib.h>
#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__ */

View file

@ -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 <winrt-glib.h> 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__ */

View file

@ -0,0 +1,7 @@
#ifndef __WINRT_GLIB_TYPES_H__
#define __WINRT_GLIB_TYPES_H__
#include <gio/gio.h>
#include "winrt-enums.h"
#endif /* __WINRT_GLIB_TYPES_H__ */

View file

@ -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__ */

View file

@ -0,0 +1,9 @@
#ifndef __WINRT_HEADERS_H__
#define __WINRT_HEADERS_H__
#include <winrt/Windows.UI.Notifications.h>
#include <winrt/Windows.Data.Xml.Dom.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#endif /* __WINRT_HEADERS_H__ */

View file

@ -0,0 +1,8 @@
#ifndef __WINRT_GLIB_H_PRIVATE__
#define __WINRT_GLIB_H_PRIVATE__
#include <glib.h>
#include "winrt.h"
#include "gobject/winrt-headers.h"
#endif // __WINRT_GLIB_H_PRIVATE__

View file

@ -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 <glib.h>
#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__ */

View file

@ -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 <winrt-glib.h> 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__ */

View file

@ -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 <glib.h>
#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__ */

View file

@ -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 <winrt-glib.h> 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__ */

View file

@ -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 <winrt-glib.h> 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__

View file

@ -0,0 +1,12 @@
#ifndef HEXIFY_HPP
#define HEXIFY_HPP
#include <cstdint>
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

View file

@ -0,0 +1,15 @@
#ifndef MAKE_ARRAY_HPP
#define MAKE_ARRAY_HPP
#include <array>
#include <algorithm>
template<std::size_t N>
inline auto make_array(const char (&from_literal)[N]) noexcept
{
static_assert(N);
std::array<char,N-1> a;
std::copy(+from_literal, from_literal+a.size(), a.begin());
return a;
}
#endif

View file

@ -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;
};

View file

@ -0,0 +1,10 @@
#ifndef OVERLOAD_HPP
#define OVERLOAD_HPP
template<typename... Callable>
struct overload : Callable...
{
overload(Callable &&... c) : Callable{std::move(c)}... {}
using Callable::operator()...;
};
#endif

View file

@ -0,0 +1,16 @@
#pragma once
#include <glib.h>
#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

View file

@ -0,0 +1 @@
#include "win32.hpp"

View file

@ -0,0 +1,47 @@
#pragma once
#include <glib.h>
#ifdef __cplusplus
#include <string>
#include <cstdint>
#include <exception>
#include <iterator>
#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<char,22+1> 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

View file

@ -0,0 +1,29 @@
#include <stringapiset.h>
#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;
}

View file

@ -0,0 +1,32 @@
#include "dyn_mod.hpp"
#include "win32.hpp"
#include <glib.h>
#include <windows.h>
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<punny_func *>(
::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;
}
}

View file

@ -0,0 +1,45 @@
#include "ginvoke.hpp"
#include "converter.hpp"
#include <winrt/base.h>
namespace glib::impl
{
std::optional<hresult> 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<char *>(ptr));
// ^ WTF? Deletion is not modification! ^
return {{ e.code(), std::move(msg) }};
}
else
return {{ e.code(), "<no error description>" }};
}
catch (...)
{
g_free(const_cast<char *>(ptr));
return {{ e.code(), "<failed to stringify error>" }};
}
}
catch (...)
{
// This is not the exception you are looking for.
return {};
}
}

View file

@ -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"))

View file

@ -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<winrtEventToken*>(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;
}

View file

@ -0,0 +1,383 @@
#include <iostream>
#include <string>
#include <string_view>
#include <vector>
#include <tuple>
#include <memory>
#include "winrt-toast-notification-private.h"
#include "winrt-event-token-private.h"
#include "converter.hpp"
template<typename Cont, typename Pred>
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<class T>
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<std::shared_ptr<Callback<NotificationCallbackActivated>>> activated{};
std::vector<std::shared_ptr<Callback<NotificationCallbackFailed>>> failed{};
std::vector<std::shared_ptr<Callback<NotificationCallbackDismissed>>> 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<winrtWindowsUINotificationsToastNotification*>(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<NotificationCallbackActivated>>(callback, context, free);
auto token = priv->notification->data.Activated([=](auto /*sender*/, winrt::Windows::Foundation::IInspectable inspectable)
{
std::wstring arguments;
std::vector<std::tuple<std::wstring, std::wstring>> user_input;
{
auto args = inspectable.try_as<winrt::Windows::UI::Notifications::IToastActivatedEventArgs>();
if (args != nullptr)
{
arguments = std::wstring(args.Arguments());
}
}
{
auto args = inspectable.try_as<winrt::Windows::UI::Notifications::IToastActivatedEventArgs2>();
if (args != nullptr)
{
for (const auto& item : args.UserInput())
{
auto value = winrt::unbox_value_or<winrt::hstring>(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<NotificationCallbackFailed>>(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<NotificationCallbackDismissed>>(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<winrtWindowsUINotificationsToastDismissalReason>(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;
});
}

View file

@ -0,0 +1,108 @@
#include <iostream>
#include <string>
#include <string_view>
#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<winrtWindowsUINotificationsToastNotifier*>(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));
}

View file

@ -0,0 +1,35 @@
#include "gobject/winrt-private.h"
#include "converter.hpp"
#include "ginvoke.hpp"
#include <windows.h>
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<ToastTemplateType>(type)).GetXml());
}
char* winrt_windows_ui_notifications_toast_notification_manager_GetTemplateContent(winrtWindowsUINotificationsToastTemplateType type) noexcept
{
return g_try_invoke(ImplGetTemplateContent, type).value_or(nullptr);
}

View file

@ -0,0 +1,130 @@
#include "shortcutcreator.h"
#include "win32.hpp"
#include "converter.hpp"
#include "ginvoke.hpp"
#include "dyn_mod.hpp"
#include <objbase.h> // COM stuff
#include <shlobj.h> // IShellLink
#include <propvarutil.h> // InitPropVariantFromString
#include <propkey.h> // PKEY_AppUserModel_ID
#include <winrt/base.h> // At least one COM header must have been previously
// included, for `winrt::create_instance` to work with the `GUID` type.
#include <memory>
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<std::uint32_t>(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<std::uint32_t>(hr));
}
auto str() const
{
wchar_t *str;
checked(dyn::PropVariantToStringAlloc,(var, &str));
return std::unique_ptr
<wchar_t, decltype(&::CoTaskMemFree)>
{ 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<IShellLinkW>(CLSID_ShellLink);
const auto file = lnk.as<IPersistFile>();
const auto store = lnk.as<IPropertyStore>();
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<wchar_t, MAX_PATH+1> 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();
}
}

View file

@ -0,0 +1,104 @@
#include <windows.h>
#include <shlobj.h>
#include "win32.hpp"
#include "converter.hpp"
#include "ginvoke.hpp"
#include <winrt/base.h>
win32_error::win32_error() noexcept
: win32_error{::GetLastError()}
{}
constexpr auto noncharacter = L'\uFFFF';
template<DWORD InitialGuess, typename Oracle>
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<DWORD>(buf.size()));
if (not maybe_len) do
{
constexpr auto dw_max = std::size_t{std::numeric_limits<DWORD>::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<DWORD>(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<std::size_t>
{
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<MAX_PATH+1>(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();
}
}

View file

@ -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<Button> _buttons = new Gee.ArrayList<Button>();
private string _header = null;
private string _body = null;
private string _appLogo = null;
private string _inlineImage = null;
private Scenario _scenario = Scenario.Basic;
public ToastNotificationBuilder() {
}
public ToastNotificationBuilder AddButton(string title, string arguments, string? imageUri = null, ActivationType activationType = ActivationType.Foreground) {
_buttons.add(new Button() { title = title, arguments = arguments, imageUri = imageUri, activationType = activationType });
return this;
}
public ToastNotificationBuilder SetHeader(string? header) {
_header = header;
return this;
}
public ToastNotificationBuilder SetBody(string? body) {
_body = body;
return this;
}
public ToastNotificationBuilder SetAppLogo(string? applogo_path) {
_appLogo = applogo_path;
return this;
}
public ToastNotificationBuilder SetInlineImage(string? image_path) {
_inlineImage = image_path;
return this;
}
public ToastNotificationBuilder SetScenario(Scenario scenario) {
_scenario = scenario;
return this;
}
public async ToastNotification Build() {
if (!_supportsModernFeatures) {
return yield BuildFromLegacyTemplate();
}
return BuildWithToastGeneric();
}
private async StanzaNode BuildStanzaFromXml(string xml) {
var reader = new Xmpp.StanzaReader.for_string(xml);
StanzaNode root_node = yield reader.read_node();
ExecuteOnAllSubNodes(root_node, (node) => {
node.ns_uri = "";
foreach (var attr in node.attributes){
attr.ns_uri = "";
}
});
return root_node;
}
private Gee.ArrayList<StanzaNode> FindRecursive(StanzaNode node, string tag_name, Gee.List<StanzaAttribute>? attributes) {
var ret = new Gee.ArrayList<StanzaNode>();
FindRecursiveInternal(node, tag_name, attributes, ret);
return ret;
}
private void FindRecursiveInternal(StanzaNode root_node, string tag_name, Gee.List<StanzaAttribute>? attributes, Gee.List<StanzaNode> list) {
if (root_node.name == tag_name) {
if (attributes != null) {
foreach (var attr in attributes) {
var node_attr = root_node.get_attribute_raw(attr.name, attr.ns_uri);
if (node_attr != null && node_attr.equals(attr)) {
list.add(root_node);
break;
}
}
}
else {
list.add(root_node);
}
}
foreach (var node in root_node.get_all_subnodes()) {
FindRecursiveInternal(node, tag_name, attributes, list);
}
}
private string ToXml(StanzaNode node) {
var namespace_state = new NamespaceState();
namespace_state.set_current("");
return node.to_xml(namespace_state);
}
private void ExecuteOnAllSubNodes(StanzaNode root_node, NodeFunction func) {
func(root_node);
foreach (var node in root_node.get_all_subnodes()) {
ExecuteOnAllSubNodes(node, func);
}
}
// Legacy templates, works on both Windows 8.1 and Windows 10:
// https://docs.microsoft.com/en-us/previous-versions/windows/apps/hh761494(v=win.10)
private async ToastNotification BuildFromLegacyTemplate() {
ToastTemplateType templateType = _header != null ? ToastTemplateType.ToastText02 : ToastTemplateType.ToastText01;
if (_appLogo != null) {
if (templateType == ToastTemplateType.ToastText02) {
templateType = ToastTemplateType.ToastImageAndText02;
} else {
templateType = ToastTemplateType.ToastImageAndText01;
}
}
var template = yield BuildStanzaFromXml(ToastNotificationManager.GetTemplateContent(templateType));
{ // add header and body
var attributes = new Gee.ArrayList<StanzaAttribute>();
attributes.add(new StanzaAttribute.build("", "id", "1"));
attributes.add(new StanzaAttribute.build("", "id", "2"));
var nodes = FindRecursive(template, "text", attributes);
foreach (var node in nodes) {
var attr = node.get_attribute_raw("id", "");
if (attr != null) {
if (attr.val == "1") {
if (templateType == ToastTemplateType.ToastText02 || templateType == ToastTemplateType.ToastImageAndText02) {
node.put_node(new StanzaNode.text(_header));
} else {
node.put_node(new StanzaNode.text(_body));
}
} else if (attr.val == "2") {
node.put_node(new StanzaNode.text(_body));
}
}
}
}
{ // add image
var nodes = FindRecursive(template, "image", null);
foreach (var node in nodes) {
var attr = node.get_attribute_raw("src", "");
if (attr != null) {
attr.val = _appLogo;
}
}
}
var xml = ToXml(template);
return new ToastNotification(xml);
}
// Modern adaptive templates for Windows 10:
// https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=builder-syntax
private ToastNotification BuildWithToastGeneric() {
var toast = new StanzaNode.build("toast", "");
if (_scenario == Scenario.IncomingCall) {
toast.put_attribute("scenario", "incomingCall");
}
{ // add content
var visual = new StanzaNode.build("visual", "");
{
var binding = new StanzaNode.build("binding", "");
binding.put_attribute("template", "ToastGeneric");
if (_header != null) {
var header = new StanzaNode.build("text", "");
header.put_node(new StanzaNode.text(_header));
binding.put_node(header);
}
if (_body != null) {
var body = new StanzaNode.build("text", "");
body.put_node(new StanzaNode.text(_body));
binding.put_node(body);
}
if (_appLogo != null) {
var appLogo = new StanzaNode.build("image", "");
appLogo.put_attribute("placement", "appLogoOverride");
appLogo.put_attribute("src", _appLogo);
binding.put_node(appLogo);
}
if (_inlineImage != null) {
var inlineImage = new StanzaNode.build("image", "");
inlineImage.put_attribute("src", _inlineImage);
binding.put_node(inlineImage);
}
visual.put_node(binding);
}
toast.put_node(visual);
}
if (_buttons.size > 0) { // add actions
var actions = new StanzaNode.build("actions", "");
foreach (var button in _buttons) {
var action = new StanzaNode.build("action", "");
action.put_attribute("content", button.title);
action.put_attribute("arguments", button.arguments);
if (button.activationType == ActivationType.Background) {
action.put_attribute("activationType", "background");
}
actions.put_node(action);
}
toast.put_node(actions);
}
return new ToastNotification(ToXml(toast));
}
}
}

View file

@ -0,0 +1,328 @@
using Dino;
using Dino.Entities;
using winrt.Windows.UI.Notifications;
using Xmpp;
using Gee;
namespace Dino.Plugins.WindowsNotification {
public class WindowsNotificationProvider : NotificationProvider, Object {
private delegate void DelegateToUi();
private static uint32 notification_counter = 0;
private ToastNotifier notifier;
private StreamInteractor stream_interactor;
private Dino.Application app;
private Gee.List<uint32> marked_for_removal;
// we must keep a reference to the notification itself or else their actions are disabled
private HashMap<uint, ToastNotification> notifications;
private Gee.List<uint32> content_notifications;
private HashMap<Conversation, Gee.List<uint32>> conversation_notifications;
private HashMap<Call, uint32> call_notifications;
public WindowsNotificationProvider(Dino.Application app, ToastNotifier notifier) {
this.notifier = notifier;
this.stream_interactor = app.stream_interactor;
this.app = app;
this.marked_for_removal = new Gee.ArrayList<uint32>();
this.content_notifications = new Gee.ArrayList<uint32>();
this.conversation_notifications = new HashMap<Conversation, Gee.List<uint32>>(Conversation.hash_func, Conversation.equals_func);
this.call_notifications = new HashMap<Call, uint32>(Call.hash_func, Call.equals_func);
this.notifications = new HashMap<uint, ToastNotification>();
}
public double get_priority() {
return 2;
}
public async void notify_message(Message message, Conversation conversation, string conversation_display_name, string? participant_display_name) {
yield notify_content_item(conversation, conversation_display_name, participant_display_name, message.body);
}
public async void notify_file(FileTransfer file_transfer, Conversation conversation, bool is_image, string conversation_display_name, string? participant_display_name) {
string text = "";
if (file_transfer.direction == Message.DIRECTION_SENT) {
text = is_image ? _("Image sent") : _("File sent");
} else {
text = is_image ? _("Image received") : _("File received");
}
string? inlineImagePath = null;
if (file_transfer.state == FileTransfer.State.COMPLETE) {
inlineImagePath = file_transfer.get_file().get_path();
}
yield notify_content_item(conversation, conversation_display_name, participant_display_name, text, inlineImagePath);
}
public async void notify_subscription_request(Conversation conversation) {
string summary = _("Subscription request");
string body = Markup.escape_text(conversation.counterpart.to_string());
var image_path = get_avatar(conversation);
var notification = yield new ToastNotificationBuilder()
.SetHeader(summary)
.SetBody(body)
.SetAppLogo(image_path)
.AddButton(_("Accept"), "accept-subscription")
.AddButton(_("Deny"), "deny-subscription")
.Build();
var notification_id = generate_id();
notification.Activated((argument, user_input) => {
run_on_ui(() => {
if (argument != null) {
app.activate_action(argument, conversation.id);
} else {
app.activate_action("open-conversation", conversation.id);
}
});
marked_for_removal.add(notification_id);
});
notification.Dismissed((reason) => marked_for_removal.add(notification_id));
notification.Failed(() => marked_for_removal.add(notification_id));
notifications[notification_id] = notification;
if (!conversation_notifications.has_key(conversation)) {
conversation_notifications[conversation] = new ArrayList<uint32>();
}
conversation_notifications[conversation].add(notification_id);
notifier.Show(notification);
}
public async void notify_connection_error(Account account, ConnectionManager.ConnectionError error) {
string summary = _("Could not connect to %s").printf(account.bare_jid.domainpart);
string body = "";
switch (error.source) {
case ConnectionManager.ConnectionError.Source.SASL:
body = _("Wrong password");
break;
case ConnectionManager.ConnectionError.Source.TLS:
body = _("Invalid TLS certificate");
break;
case ConnectionManager.ConnectionError.Source.STREAM_ERROR:
body = "Stream Error";
break;
case ConnectionManager.ConnectionError.Source.CONNECTION:
body = "Connection";
break;
}
var notification = yield new ToastNotificationBuilder()
.SetHeader(summary)
.SetBody(body)
.Build();
var notification_id = generate_id();
notification.Activated((argument, user_input) => marked_for_removal.add(notification_id));
notification.Dismissed((reason) => marked_for_removal.add(notification_id));
notification.Failed(() => marked_for_removal.add(notification_id));
notifications[notification_id] = notification;
notifier.Show(notification);
}
public async void notify_muc_invite(Account account, Jid room_jid, Jid from_jid, string inviter_display_name) {
Conversation direct_conversation = new Conversation(from_jid, account, Conversation.Type.CHAT);
string display_room = room_jid.bare_jid.to_string();
string summary = _("Invitation to %s").printf(display_room);
string body = _("%s invited you to %s").printf(inviter_display_name, display_room);
var image_path = get_avatar(direct_conversation);
var notification = yield new ToastNotificationBuilder()
.SetHeader(summary)
.SetBody(body)
.SetAppLogo(image_path)
.AddButton(_("Accept"), "open-muc-join")
.AddButton(_("Deny"), "deny-invite")
.Build();
var notification_id = generate_id();
var group_conversation_id = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(room_jid, account, Conversation.Type.GROUPCHAT).id;
notification.Activated((argument, user_input) => {
run_on_ui(() => {
if (argument != null) {
app.activate_action(argument, group_conversation_id);
} else {
app.activate_action("open-muc-join", group_conversation_id);
}
});
marked_for_removal.add(notification_id);
});
notification.Dismissed((reason) => marked_for_removal.add(notification_id));
notification.Failed(() => marked_for_removal.add(notification_id));
notifications[notification_id] = notification;
notifier.Show(notification);
}
public async void notify_voice_request(Conversation conversation, Jid from_jid) {
string display_name = Dino.get_participant_display_name(stream_interactor, conversation, from_jid);
string display_room = Dino.get_conversation_display_name(stream_interactor, conversation, _("%s from %s"));
string summary = _("Permission request");
string body = _("%s requests the permission to write in %s").printf(display_name, display_room);
var image_path = get_avatar(conversation);
var notification = yield new ToastNotificationBuilder()
.SetHeader(summary)
.SetBody(body)
.SetAppLogo(image_path)
.AddButton(_("Accept"), "accept-voice-request")
.AddButton(_("Deny"), "deny-voice-request")
.Build();
var notification_id = generate_id();
notification.Activated((argument, user_input) => {
if (argument != null) {
run_on_ui(() => app.activate_action(argument, conversation.id));
}
marked_for_removal.add(notification_id);
});
notification.Dismissed((reason) => marked_for_removal.add(notification_id));
notification.Failed(() => marked_for_removal.add(notification_id));
notifications[notification_id] = notification;
notifier.Show(notification);
}
private async void notify_content_item(Conversation conversation, string conversation_display_name, string? participant_display_name, string body_, string? inlineImagePath = null) {
clear_marked();
string body = body_;
if (participant_display_name != null) {
body = @"$participant_display_name: $body";
}
var image_path = get_avatar(conversation);
var notification = yield new ToastNotificationBuilder()
.SetHeader(conversation_display_name)
.SetBody(body)
.SetAppLogo(image_path)
.SetInlineImage(inlineImagePath)
.Build();
var notification_id = generate_id();
notification.Activated((argument, user_input) => {
run_on_ui(() => app.activate_action("open-conversation", conversation.id));
marked_for_removal.add(notification_id);
});
notification.Dismissed((reason) => marked_for_removal.add(notification_id));
notification.Failed(() => marked_for_removal.add(notification_id));
notifications[notification_id] = notification;
notifier.Show(notification);
content_notifications.add(notification_id);
}
private string? get_avatar(Conversation conversation) {
var avatar_manager = app.stream_interactor.get_module(AvatarManager.IDENTITY);
return avatar_manager.get_avatar_filepath(conversation.account, conversation.counterpart);
}
public async void retract_content_item_notifications() {
foreach (uint id in content_notifications) {
remove_notification(id);
}
content_notifications.clear();
}
public async void retract_conversation_notifications(Conversation conversation) {
if (conversation_notifications.has_key(conversation)) {
var conversation_items = conversation_notifications[conversation];
foreach (uint id in conversation_items) {
remove_notification(id);
}
conversation_items.clear();
}
}
public async void notify_call(Call call, Conversation conversation, bool video, bool multiparty, string conversation_display_name) {
string summary = Markup.escape_text(conversation_display_name);
string body = video ? _("Incoming video call") : _("Incoming call");
if (multiparty) {
body = video ? _("Incoming video group call") : _("Incoming group call");
}
var image_path = get_avatar(conversation);
var notification = yield new ToastNotificationBuilder()
.SetHeader(summary)
.SetBody(body)
.SetAppLogo(image_path)
.AddButton(_("Accept"), "accept-call")
.AddButton(_("Deny"), "reject-call", null, ActivationType.Background)
.SetScenario(Scenario.IncomingCall)
.Build();
var notification_id = generate_id();
notification.Activated((argument, user_input) => {
run_on_ui(() => {
if (argument != null) {
app.activate_action(argument, new Variant.tuple(new Variant[] { new Variant.int32(conversation.id), new Variant.int32(call.id) }));
} else {
app.activate_action("open-conversation", conversation.id);
}
});
marked_for_removal.add(notification_id);
});
notification.Dismissed((reason) => marked_for_removal.add(notification_id));
notification.Failed(() => marked_for_removal.add(notification_id));
notifications[notification_id] = notification;
call_notifications[call] = notification_id;
notifier.Show(notification);
}
public async void retract_call_notification(Call call, Conversation conversation) {
if (call_notifications.has_key(call)) {
var notification_id = call_notifications[call];
remove_notification(notification_id);
call_notifications.unset(call);
}
}
private void clear_marked() {
foreach (var id in marked_for_removal) {
remove_notification(id);
}
marked_for_removal.clear();
}
private void remove_notification(uint id) {
ToastNotification notification = null;
notifications.unset(id, out notification);
if (notification != null) {
notifier.Hide(notification);
}
}
private uint32 generate_id() {
return AtomicUint.add(ref notification_counter, 1);
}
private void run_on_ui(owned DelegateToUi func) {
Idle.add(() => { func(); return false; }, GLib.Priority.HIGH);
}
}
}

View file

@ -0,0 +1,35 @@
using Dino.Entities;
using Dino.Plugins.WindowsNotification.Vapi;
using winrt.Windows.UI.Notifications;
using Xmpp;
namespace Dino.Plugins.WindowsNotification {
public class Plugin : RootInterface, Object {
private static string AUMID = "org.dino.Dino";
public void registered(Dino.Application app) {
if (!winrt.InitApartment())
{
// log error, return
}
if (!Win32Api.SetProcessAumid(AUMID))
{
// log error, return
}
if (!ShortcutCreator.EnsureAumiddedShortcutExists(AUMID))
{
// log error, return
}
app.stream_interactor.get_module(NotificationEvents.IDENTITY)
.register_notification_provider(new WindowsNotificationProvider(app, new ToastNotifier(AUMID)));
}
public void shutdown() {
}
}
}

View file

@ -0,0 +1,3 @@
public Type register_plugin(Module module) {
return typeof (Dino.Plugins.WindowsNotification.Plugin);
}

View file

@ -0,0 +1,9 @@
[CCode (cheader_filename = "enums.h")]
namespace Dino.Plugins.WindowsNotification.Vapi.Enums {
[CCode (cname = "Dismissed_Reason", cprefix = "Dismissed_Reason_")]
public enum DismissedReason {
Activated,
ApplicationHidden,
TimedOut
}
}

View file

@ -0,0 +1,5 @@
[CCode (cheader_filename = "shortcutcreator.h")]
namespace Dino.Plugins.WindowsNotification.Vapi.ShortcutCreator {
[CCode (cname = "EnsureAumiddedShortcutExists")]
public bool EnsureAumiddedShortcutExists(string aumid);
}

View file

@ -0,0 +1,9 @@
[CCode (cheader_filename = "win32.h")]
namespace Dino.Plugins.WindowsNotification.Vapi.Win32Api {
[CCode (cname = "IsWindows10")]
public bool IsWindows10();
[CCode (cname = "SetProcessAumid")]
public bool SetProcessAumid(string aumid);
}

View file

@ -0,0 +1,13 @@
[CCode (cheader_filename = "gobject/winrt-glib.h")]
namespace winrt {
public bool InitApartment();
[CCode (type_id = "winrt_event_token_get_type ()")]
public class EventToken : GLib.Object {
[CCode (has_construct_function = false)]
public EventToken();
public int64 value { get; }
[CCode(cname = "winrt_event_token_operator_bool")]
public bool IsValid();
}
}

View file

@ -0,0 +1,68 @@
using winrt;
[CCode (cheader_filename = "gobject/winrt-glib.h")]
namespace winrt.Windows.UI.Notifications {
[CCode (type_id = "winrt_windows_ui_notifications_toast_dismissal_reason_get_type ()")]
public enum ToastDismissalReason
{
Activated,
ApplicationHidden,
TimedOut
}
[CCode (type_id = "winrt_windows_ui_notifications_toast_template_type_get_type ()")]
public enum ToastTemplateType
{
ToastImageAndText01,
ToastImageAndText02,
ToastImageAndText03,
ToastImageAndText04,
ToastText01,
ToastText02,
ToastText03,
ToastText04
}
[CCode (cname = "NotificationCallbackFailed", has_target = true)]
public delegate void NotificationCallbackFailed();
[CCode (cname = "NotificationCallbackActivated", has_target = true)]
public delegate void NotificationCallbackActivated(string? arguments, string[]? userInput);
[CCode (cname = "NotificationCallbackDismissed", has_target = true)]
public delegate void NotificationCallbackDismissed(ToastDismissalReason reason);
[CCode (type_id = "winrt_windows_ui_notifications_toast_notification_get_type ()")]
public class ToastNotification : GLib.Object {
public ToastNotification(string doc);
public bool ExpiresOnReboot { get; set; }
public EventToken Activated(owned NotificationCallbackActivated handler);
public void RemoveActivated(EventToken token);
public EventToken Failed(owned NotificationCallbackFailed handler);
public void RemoveFailed(EventToken token);
public EventToken Dismissed(owned NotificationCallbackDismissed handler);
public void RemoveDismissed(EventToken token);
public string GetTag();
public void SetTag(string tag);
public string GetGroup();
public void SetGroup(string group);
}
[CCode (type_id = "winrt_windows_ui_notifications_toast_notifier_get_type ()")]
public class ToastNotifier : GLib.Object {
public ToastNotifier(string aumid);
public void Show(ToastNotification notification);
public void Hide(ToastNotification notification);
}
[Compact]
[CCode]
public class ToastNotificationManager {
public static string GetTemplateContent(ToastTemplateType type);
}
}

View file

@ -0,0 +1,15 @@
Dino - Modern Jabber/XMPP Client using GTK+/Vala
Copyright (C) 2016-2020 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 <http://www.gnu.org/licenses/>.

View file

@ -0,0 +1,68 @@
Unicode True
RequestExecutionLevel user
SetCompressor /SOLID lzma
!define MUI_PRODUCT "Dino"
!define MUI_PRODUCT_NAME ${MUI_PRODUCT}
!define MUI_BRANDINGTEXT ${MUI_PRODUCT}
!define PRODUCT_WEBSITE "https://dino.im"
!define MUI_ICON "win64-dist/dino.ico"
!define ICON "win64-dist/dino.ico"
!define MUI_COMPONENTSPAGE_NODESC
# Modern Interface
!include "MUI2.nsh"
!insertmacro MUI_PAGE_LICENSE "LICENSE_SHORT"
!insertmacro MUI_PAGE_INSTFILES
!include "english.nsh"
Name ${MUI_PRODUCT}
BrandingText "Communicating happiness"
# define installer name
OutFile "dino-installer.exe"
# set install directory
InstallDir $APPDATA\Dino
Section
# Install all files
SetOutPath $INSTDIR
File /r win64-dist\*.*
# define uninstaller name
WriteUninstaller $INSTDIR\uninstaller.exe
# Create a shortcut for startmenu
CreateDirectory "$SMPROGRAMS\Dino"
CreateShortcut "$SMPROGRAMS\Dino\Dino.lnk" "$INSTDIR\bin\dino.exe" "" "$INSTDIR\dino.ico"
CreateShortcut "$SMPROGRAMS\Dino\Uninstaller.lnk" "$INSTDIR\uninstaller.exe"
CreateShortcut "$SMPROGRAMS\Dino\License.lnk" "notepad.exe" "$INSTDIR\LICENSE"
CreateShortcut "$SMPROGRAMS\Dino\Dino website.lnk" "https://dino.im" "" "$INSTDIR\dino.ico"
# Create a shortcut for desktop
CreateShortCut "$DESKTOP\Dino.lnk" "$INSTDIR\bin\dino.exe" "" "$INSTDIR\dino.ico"
# set application ID
# No "ApplicationID" plugin for NSIS MINGW64
# ApplicationID::Set "$SMPROGRAMS\Dino\Dino.lnk" "Dino" "true"
SectionEnd
# Uninstaller section
Section "Uninstall"
# Delete startmenu folder
RMDir /r "$SMPROGRAMS\Dino"
# Always delete uninstaller first
Delete $INSTDIR\uninstaller.exe
# now delete installed file
Delete $INSTDIR\*
# Delete the directory
RMDir /r $INSTDIR
SectionEnd

View file

@ -0,0 +1,3 @@
!define MUI_TEXT_LICENSE_TITLE "Dino License"
!define MUI_TEXT_LICENSE_SUBTITLE "Please read the license short summary carefully"
!insertmacro MUI_LANGUAGE "English"