Compare commits

...

179 commits

Author SHA1 Message Date
Maxim Logaev 75092caced
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>
2024-03-12 20:01:36 +03:00
Psayker ca1f97e243 Move download yolort headers logic into prepare stage, delete yolort download script 2024-03-12 16:20:13 +03:00
Maxim Logaev 9c15a81b40 Fixed build dependencies
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-11 17:10:07 +03:00
Maxim Logaev 3848cf3129 Added build-installer target to build-win64.sh
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-06 23:01:49 +03:00
Maxim Logaev 43502806bc Removed unnecessary installer files
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-06 21:19:13 +03:00
Maxim Logaev b886e2a2f2 Fixed dist-install dir
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-06 21:11:15 +03:00
Maxim Logaev 4674e90d16 Added basic support for NSIS installer
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-06 20:42:19 +03:00
Maxim Logaev a3171e12df Added README-WIN64.md
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-05 23:13:25 +03:00
Maxim Logaev 5f500b4d42 Added build script for windows
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-05 22:58:00 +03:00
Maxim Logaev f9fd66dba5 Merge branch 'origin/master' into master-windows-changes 2024-03-05 15:07:52 +03:00
LAGonauta 1c156e8c9f Fix GPGME 2023-10-09 10:54:55 -04:00
LAGonauta f6fe383ce8 Merge remote-tracking branch 'upstream/master' into master-windows-changes 2023-10-09 10:54:15 -04:00
LAGonauta 49f331627d Merge remote-tracking branch 'upstream/master' into master-windows-changes 2023-07-17 17:28:47 -04:00
LAGonauta 7603990740 Merge remote-tracking branch 'upstream/master' into master-windows-changes 2023-04-24 19:01:02 -03:00
LAGonauta 7872d21f03 Merge remote-tracking branch 'upstream/master' into master-windows-changes 2023-04-21 20:43:40 -03:00
LAGonauta 86182a7db9 Check YoloRT checksum before building 2023-02-12 11:17:57 -03:00
LAGonauta 82e6937f21 Merge remote-tracking branch 'upstream/master' into master-windows-changes 2023-02-12 10:59:16 -03:00
LAGonauta 3d61f175a6 Merge remote-tracking branch 'upstream/master' into master-windows-changes 2023-01-30 08:53:45 -03:00
LAGonauta 471f8c5f4a Merge remote-tracking branch 'upstream/master' into master-windows-changes 2023-01-08 15:41:07 -03:00
LAGonauta fb05c83c8e Merge remote-tracking branch 'upstream/master' into master-windows-changes 2022-12-28 09:13:59 -03:00
LAGonauta ef41c27cb2 Merge remote-tracking branch 'upstream/master' into master-windows-changes 2022-10-15 13:00:56 -03:00
LAGonauta e638ef0694 Fixed using GTK3 instead of GTK4 2022-08-25 09:29:47 -03:00
LAGonauta a7f7a6d388 Merge remote-tracking branch 'upstream/master' into master-windows-changes 2022-08-25 08:54:04 -03:00
LAGonauta d507808e40 Automatically set PANGOCAIRO_BACKEND to fontconfig on win32 2022-06-17 18:37:49 -03:00
LAGonauta 2d82a4c195 Removed version from Dino executable
We need a better way to get the version number
2022-06-17 18:37:34 -03:00
LAGonauta c29032bcc6 :Merge remote-tracking branch 'upstream/master' into master-windows-changes 2022-06-05 11:12:23 -03:00
LAGonauta 90bd9df891 Merge remote-tracking branch 'upstream/master' into master-windows-changes 2022-02-25 07:02:20 -03:00
LAGonauta d10badd690 Ignore wasapi devices as they do not work well yet 2022-02-25 07:02:06 -03:00
LAGonauta 11828cc2cf no need to ignore wasapi 2022-02-09 21:09:44 -03:00
LAGonauta a583a44994 fix call notifications buttons not working 2022-02-09 21:01:26 -03:00
LAGonauta dd71e943c3 Merge remote-tracking branch 'upstream/master' into master-windows-changes 2022-02-08 20:32:22 -03:00
LAGonauta b8b74817f7 Merge remote-tracking branch 'upstream/master' into master-windows-changes
# Conflicts:
#	main/src/ui/conversation_content_view/file_widget.vala
#	plugins/CMakeLists.txt
#	plugins/rtp/src/device.vala
#	plugins/rtp/src/plugin.vala
2022-02-08 18:33:09 -03:00
mjk 43fd04ce41 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.
2022-02-08 18:09:49 -03:00
Felipe 733d612b18
Merge pull request #5 from danielreuther/master-windows-changes-gcc11-fix
Fix compilation on GCC 11
2022-02-08 18:04:58 -03:00
Daniel Reuther 5a335f89d3 Fix compilation on gcc 11 2021-10-23 02:45:52 +02:00
LAGonauta c57fc768aa Do not generate WinRT headers, just download them on build 2021-06-19 11:43:12 -03:00
LAGonauta 88a376c1c2 Build YoloRT on project build 2021-06-19 08:13:39 -03:00
LAGonauta 97753bd5c4 Remove YoloRT from tree 2021-06-19 06:58:40 -03:00
LAGonauta d7118c1b93 Allow devices with properties and use has_classes 2021-06-18 07:46:39 -03:00
LAGonauta 96fbbdd8bb Use correct generic type for ArrayList
Nullable crashes Dino
2021-06-10 20:03:30 -03:00
LAGonauta 4b0c3b8ef0 Add initial call notifications 2021-06-09 07:20:56 -03:00
LAGonauta ee11a2dbcf 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.
2021-06-09 07:20:45 -03:00
LAGonauta 064541dd2b 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.
2021-06-09 07:20:33 -03:00
LAGonauta 854818133e DelegateToUi must be an owned function 2021-06-09 07:20:14 -03:00
LAGonauta 878bf9ac43 Allow null header, body, applogo, and image on notification builder 2021-06-09 07:20:14 -03:00
LAGonauta e6a521ada9 Add support for inline images to notification 2021-06-09 07:20:14 -03:00
LAGonauta 359dcbf70c Add support for adaptive Windows 10 notifications 2021-06-09 07:20:14 -03:00
mjk c40775b7da comment out unused arguments [-Wunused-parameter] 2021-06-09 07:20:13 -03:00
mjk 934b492357 init Callback completely always
The `token` pointer was left dangerously uninitialized after construction.
2021-06-09 07:20:12 -03:00
mjk 0258980977 replace lists with vectors 2021-06-09 07:20:12 -03:00
mjk 8034a6f344 remove the no-longer-needed -municode compile option 2021-06-09 07:20:10 -03:00
mjk c89671af04 log SetCurrentProcessExplicitAppUserModelID errors 2021-06-09 07:20:10 -03:00
mjk 822b91f40c stop checking for empty AUMIDs
The downstream code handles them just fine.
2021-06-09 07:20:09 -03:00
mjk 332216e63c reflow/respace 2021-06-09 07:20:09 -03:00
mjk 7b6f3ea9f6 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.
2021-06-09 07:20:04 -03:00
mjk 8988b77d57 don't use FALSE as a null pointer constant 2021-06-09 07:20:03 -03:00
mjk 987eb672d5 fix / work around uninitialized fields warnings 2021-06-09 07:20:03 -03:00
mjk 1698d15f7d silence enum stringification warnings by first casting to underlying types 2021-06-09 07:20:02 -03:00
mjk 17c1172d84 work around a (pedantic) format specifier warning 2021-06-09 07:20:02 -03:00
mjk 8f8dfa2e70 move module loading functions out of shortcutcreator.cpp 2021-06-09 07:20:02 -03:00
mjk e8d84d2eda handle empty menu-relative shortcut paths 2021-06-09 07:20:01 -03:00
mjk f1bcb6604f 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*.
2021-06-09 07:20:01 -03:00
mjk c855d5e7cb protect GetTemplateContent callers from exceptions 2021-06-09 07:20:00 -03:00
mjk ed7c23c8d2 generalize glib::try_invoke to any return-by-value type and void 2021-06-09 07:19:59 -03:00
mjk b1cb64d716 add nullary version of g_try_invoke macro 2021-06-09 07:19:58 -03:00
mjk da424fb73b fiddle with punctuation 2021-06-09 07:19:58 -03:00
mjk 39b51b2d4c make glib::impl::varstring less explosive 2021-06-09 07:19:57 -03:00
mjk 7cb6b3f4b3 fix UB in glib::describe_arguments called with no arguments
Makes failure logging of nullary invokables non-crashy.
2021-06-09 07:19:56 -03:00
mjk 15abe70fc6 handle theoretical future failures of wsview_to_char 2021-06-09 07:19:56 -03:00
mjk ab6ce05aec handle empty hresult_error message specially 2021-06-09 07:19:55 -03:00
mjk c44b1daaf7 make meta-error messages more precise 2021-06-09 07:19:54 -03:00
mjk 5e50d1d2d7 remove an unused include 2021-06-09 07:19:53 -03:00
mjk d3214188d5 use lower-case 0x prefix for hresult code formatting everywhere 2021-06-09 07:19:51 -03:00
mjk 229ff697c4 move some whitespace around 2021-06-09 07:19:50 -03:00
mjk c3624f8490 handle exe paths longer than 259 chars 2021-06-09 07:19:48 -03:00
mjk 597061aed1 work around GetEnvironmentVariable not resetting last error 2021-06-09 07:19:48 -03:00
mjk fd035084de unimplement accidentally implemented wide string overloads of describe_argument 2021-06-09 07:19:47 -03:00
LAGonauta 426f997f9b Fix notifications not hiding 2021-06-09 07:19:47 -03:00
LAGonauta c60e0a828f Add README.md to Windows notification plugin 2021-06-09 07:19:46 -03:00
LAGonauta cc0e206c27 Removed unused library on linker
It is loaded dynamically
2021-06-09 07:19:45 -03:00
LAGonauta 60791cb3f0 Add ginvoke to CMakeLists 2021-06-09 07:19:45 -03:00
mjk f9a3b28624 switch to runtime loading of PropVariantToStringAlloc
Now it really should work.
2021-06-09 07:19:44 -03:00
mjk cb3c6874bc add restoration of shortcut's target path 2021-06-09 07:19:44 -03:00
mjk eeda464ca9 rewrite shortcut management code with RAII, error logging and exceptions
It actually works now.
2021-06-09 07:19:43 -03:00
mjk f193948f4e constify all the things 2021-06-09 07:19:43 -03:00
mjk 401c4a1bb1 change some local functions' signatures 2021-06-09 07:19:43 -03:00
mjk 34519e96bd make GetEnv more robust and not limit length of variables 2021-06-09 07:19:42 -03:00
mjk fb754b0d43 generalize GetShortcutPath into GetEnv 2021-06-09 07:19:42 -03:00
mjk 2eb1eea06e clarify GetCurrentModulePath's name 2021-06-09 07:19:41 -03:00
mjk 5b40d166d2 make GetCurrentModulePath and GetShortcutPath throw win32 errors 2021-06-09 07:19:41 -03:00
mjk 2ad659f777 clarify some entry points' names 2021-06-09 07:19:40 -03:00
mjk b21066c89e mark exception-safe C entry points as such 2021-06-09 07:19:40 -03:00
mjk 1bd1376cea stop exceptions from crossing ABI boundary in a few places 2021-06-09 07:19:40 -03:00
mjk f1b5633ce6 introduce try_invoke -- a logging exception catcher 2021-06-09 07:19:39 -03:00
LAGonauta 3d9dcbcf87 Valac think that getters are always owned by the struct 2021-06-09 07:19:38 -03:00
LAGonauta 8856fcbceb Use g_new0 and g_free to generate raw strings 2021-06-09 07:19:37 -03:00
LAGonauta 94944a9e2a Use async 2021-06-09 07:19:37 -03:00
LAGonauta 7de1c01fe8 Add comment to builder 2021-06-09 07:19:36 -03:00
LAGonauta 0f555da7a9 Fixed crash with multiple notifications
Sometimes an invalid function pointer was called with an invalid context
2021-06-09 07:19:35 -03:00
LAGonauta f2c689fa12 Delegate activate_action to UI thread 2021-06-09 07:19:34 -03:00
LAGonauta 839d2a5316 Initial notification provider using WinRT
Crashes when activating actions, might be related to threads.
2021-06-09 07:19:32 -03:00
LAGonauta be0e1841b8 Anitial builder 2021-06-09 07:19:32 -03:00
LAGonauta 2476b5e04b Initial experiments with notification XML building 2021-06-09 07:19:32 -03:00
LAGonauta bc9b9b95e0 Add template getter 2021-06-09 07:19:31 -03:00
LAGonauta 9d9b9e8e42 Rename plugin vala file 2021-06-09 07:19:31 -03:00
LAGonauta 0153953b5d Rename enums to better match what Vala expects 2021-06-09 07:19:30 -03:00
LAGonauta 363b200272 Add template type enum 2021-06-09 07:19:28 -03:00
LAGonauta c6a96fc025 Cleanup dismissed actions on toast notification finalizer 2021-06-09 07:19:27 -03:00
LAGonauta 898470ed23 Add failed and dimissed actions 2021-06-09 07:19:25 -03:00
LAGonauta 6cdaad315a Fields must be created in the private struct
Also change unordered_map to list, we do not need hashing and stuff.
2021-06-09 07:19:24 -03:00
LAGonauta 91d61843f2 No need to enable coroutines 2021-06-09 07:19:22 -03:00
LAGonauta c4f526329d Remove unity compilation unit 2021-06-09 07:19:21 -03:00
LAGonauta 40bf3d2fd4 Actions can stack 2021-06-09 07:19:20 -03:00
LAGonauta 7fd918f32d Fix string conversion 2021-06-09 07:19:19 -03:00
LAGonauta 1d1b00222f Add toastnotifier 2021-06-09 07:19:17 -03:00
LAGonauta 8df226ec29 Increase ref on event token 2021-06-09 07:19:15 -03:00
LAGonauta 13e0a5c0c4 Use string_view 2021-06-09 07:19:14 -03:00
LAGonauta ca0cab0e36 Initial code to allow buttons and text 2021-06-09 07:19:13 -03:00
LAGonauta ae9671716f Initial GObject wrapper for WinRT notifications
Still missing a lot of stuff
2021-06-09 07:19:11 -03:00
LAGonauta ce0deed0dc Initial callback support 2021-06-09 07:19:09 -03:00
LAGonauta 95051d304a Initial code for using winrt headers 2021-06-09 07:19:08 -03:00
LAGonauta 915dd4c738 Add MIT licensed winrt headers 2021-06-09 07:19:06 -03:00
LAGonauta e3356bb3aa Do not use GLib to open links in messages
Use ShellExecute
2021-06-09 07:19:05 -03:00
LAGonauta 03774a3756 Fix muc invite and voide request not working 2021-06-09 07:19:05 -03:00
LAGonauta 2d63c8ae77 Rename field 2021-06-09 07:19:04 -03:00
LAGonauta 96f96ead7e Use list with all notifications 2021-06-09 07:19:04 -03:00
LAGonauta 5f2e636868 Add notification retraction 2021-06-09 07:19:03 -03:00
LAGonauta d8bb9897a4 Add support for custom actions on notification 2021-06-09 07:19:02 -03:00
LAGonauta 08f2391acc Initial plugin using new notification provider 2021-06-09 07:19:02 -03:00
LAGonauta eae628758c Use VAPI and generate template in-app 2021-06-09 07:19:01 -03:00
LAGonauta 931f09504d Added 32-bit wintoast linker library 2021-06-09 07:19:01 -03:00
LAGonauta 5702b323c9 Use dynamic linking instead of runtime loading
Also made me notice that the signature of the function with the callback was wrong. Oops.
2021-06-09 07:19:00 -03:00
LAGonauta 198bce4a84 Allow null image_path 2021-06-09 07:18:59 -03:00
LAGonauta 8553a7cd86 Add callback support 2021-06-09 07:18:59 -03:00
LAGonauta 642ed6ab1b Convert C code to Vala 2021-06-09 07:18:58 -03:00
LAGonauta d36de2b9ea Use code from Dino.Ui.Util 2021-06-09 07:18:56 -03:00
LAGonauta 49978edebf Clean up 2021-06-09 07:18:55 -03:00
LAGonauta 65a26bce53 Refactor windows-notification plugin 2021-06-09 07:18:53 -03:00
LAGonauta 1d4bb774a5 Initial notification support 2021-06-09 07:18:51 -03:00
LAGonauta 3dd19fad71 Use last_index_of instead of index_of 2021-06-09 07:18:47 -03:00
LAGonauta 4a3306b479 Allow 32-bit linking
Win32 apis are __stdcall
2021-06-09 07:18:45 -03:00
LAGonauta 0b9bebd97e Use Dino.Util.get_content_type also on preview 2021-06-09 07:18:45 -03:00
LAGonauta 24890ca38a Export all plugin symbols on Windows 2021-06-09 07:18:44 -03:00
LAGonauta 50b2a0dc0a Do not hardcode GPG path on Windows 2021-06-09 07:18:43 -03:00
LAGonauta a6515ed826 Add implicit link directories to package HINT path on MingW
Instead of blacklisting those libraries
2021-06-09 07:18:43 -03:00
LAGonauta e7fd7f4726 Add --export-all-symbols to Windows compilation 2021-06-09 07:18:42 -03:00
LAGonauta 9022ac596b Every call to CoInitialize() must be balanced
with a call to CoUninitialize()
2021-06-09 07:18:42 -03:00
LAGonauta 823df3ed70 Add WIN32 fonts as a plugin 2021-06-09 07:18:26 -03:00
LAGonauta 6d5ee03e9e Set Windows executable version from PROJECT_VERSION 2021-06-09 07:17:22 -03:00
LAGonauta 1673ec3481 Added information and Dino icon to Windows executable 2021-06-09 07:17:22 -03:00
LAGonauta 16d0cc6fd9 Set alternate file stream for downloaded files. 2021-06-09 07:17:21 -03:00
LAGonauta 03f17b8f39 Fixed some Windows not appearing when opening file 2021-06-09 07:17:21 -03:00
LAGonauta 25673ab145 Use slight larger font on Windows so it matches Linux style
Also fixes some fuzzy fonts.
2021-06-09 07:17:21 -03:00
LAGonauta 8cb15fc325 Use ShellExecute instead of AppInfo to open files on Windows 2021-06-09 07:17:21 -03:00
LAGonauta 281a84a14c Add support for OpenPGP on Windows 2021-06-09 07:17:20 -03:00
LAGonauta e2fd821bb4 Add missing _WIN32 2021-06-09 07:17:20 -03:00
LAGonauta 0d384f83ae Added _WIN32 define to VALAC on Windows 2021-06-09 07:17:20 -03:00
LAGonauta 1841b348f1 Do not search for the built-in libraries when compiling with MINGW 2021-06-09 07:17:19 -03:00
LAGonauta 5f5b8fdfa0 fix build on newest MSYS2 2021-06-09 07:17:19 -03:00
LAGonauta eb7cf3297c Windows compatibility Tweaks 2021-06-09 07:17:18 -03:00
Martin Dosch 5d551360df Fix syntax error for setting AppID. 2020-11-06 14:12:50 +01:00
Martin Dosch a94acccd1d Add AppID (untested). 2020-10-29 13:47:26 +01:00
Martin Dosch d0d676e72d Add compression to achieve smaller installer size. 2020-09-18 16:51:41 +02:00
Martin Dosch 81721307ca Remove installation type "OpenPGP" support
This is no longer needed (see previous commit)
but was forgotten to remove in the previous
commit.
2020-07-10 17:04:13 +02:00
Martin Dosch 2d73f97d34 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.
2020-07-10 16:55:00 +02:00
Martin Dosch e24aa9aabc Add option to install without OpenPGP plugin 2020-06-28 09:00:03 +02:00
Martin Dosch 26a85ac109 Add german language. 2020-06-28 07:13:52 +02:00
Martin Dosch 8adf6731e7 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
2020-05-19 08:38:17 +02:00
Martin Dosch 9154edaee1 Add dino logo again
The dino logo for the startmenu was accidentally
no longer included since the last commit.
2020-05-19 01:05:22 +02:00
Martin Dosch fd916bd6e6 Prevent duplicated DLLs 2020-05-19 00:55:14 +02:00
Martin Dosch 48619ee84d Add startmenu folder with several items
Added a startmenu folder with the following items:
* Dino launcher
* License
* Link to Dino website
* Uninstaller
2020-05-05 22:20:49 +02:00
Martin Dosch 70900cd73e Add license to windows installer 2020-05-05 19:17:45 +02:00
Martin Dosch 64260ee8c0 Merge branch 'master' into master-windows-changes 2020-05-03 11:11:19 +02:00
Martin Dosch 246000ce6a Fix typo in Dino slogan 2020-05-03 01:26:59 +02:00
Martin Dosch 9779abe701 Prepare signing
Collected some infos regarding signing
a windows build.
2020-05-03 01:01:30 +02:00
Martin Dosch 3ffec591a9 Revert "Prepare signing"
I copied the files into the wrong folder…
… it's late, sorry.

This reverts commit 7d6b9e7f4c.
2020-05-03 00:59:42 +02:00
Martin Dosch 7d6b9e7f4c Prepare signing
Collected some infos regarding signing
a windows build.
2020-05-03 00:55:59 +02:00
Martin Dosch 43f118bd4e 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
2020-05-02 22:03:20 +02:00
LAGonauta 057a72cd9f Windows compatibility Tweaks 2020-05-02 12:48:48 -03:00
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")
add_executable(dino ${MAIN_VALA_C} ${MAIN_GRESOURCES_TARGET}) 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})
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"