From ac23ce32a1376fd0358e1dc654b0940befbef7cd Mon Sep 17 00:00:00 2001 From: Patrick Pacher <patrick.pacher@gmail.com> Date: Wed, 20 Mar 2024 11:10:32 +0100 Subject: [PATCH] Migrate notifier from portmaster-ui to cmds/notifier, remove some duplicated code, move assets to assets/data and add a small go package in assets to allow embedding icons --- Earthfile | 9 +- .../{ => data}/fonts/Roboto-300/LICENSE.txt | 0 .../fonts/Roboto-300/Roboto-300.eot | Bin .../fonts/Roboto-300/Roboto-300.svg | 0 .../fonts/Roboto-300/Roboto-300.ttf | Bin .../fonts/Roboto-300/Roboto-300.woff | Bin .../fonts/Roboto-300/Roboto-300.woff2 | Bin .../fonts/Roboto-300italic/LICENSE.txt | 0 .../Roboto-300italic/Roboto-300italic.eot | Bin .../Roboto-300italic/Roboto-300italic.svg | 0 .../Roboto-300italic/Roboto-300italic.ttf | Bin .../Roboto-300italic/Roboto-300italic.woff | Bin .../Roboto-300italic/Roboto-300italic.woff2 | Bin .../{ => data}/fonts/Roboto-500/LICENSE.txt | 0 .../fonts/Roboto-500/Roboto-500.eot | Bin .../fonts/Roboto-500/Roboto-500.svg | 0 .../fonts/Roboto-500/Roboto-500.ttf | Bin .../fonts/Roboto-500/Roboto-500.woff | Bin .../fonts/Roboto-500/Roboto-500.woff2 | Bin .../fonts/Roboto-500italic/LICENSE.txt | 0 .../Roboto-500italic/Roboto-500italic.eot | Bin .../Roboto-500italic/Roboto-500italic.svg | 0 .../Roboto-500italic/Roboto-500italic.ttf | Bin .../Roboto-500italic/Roboto-500italic.woff | Bin .../Roboto-500italic/Roboto-500italic.woff2 | Bin .../{ => data}/fonts/Roboto-700/LICENSE.txt | 0 .../fonts/Roboto-700/Roboto-700.eot | Bin .../fonts/Roboto-700/Roboto-700.svg | 0 .../fonts/Roboto-700/Roboto-700.ttf | Bin .../fonts/Roboto-700/Roboto-700.woff | Bin .../fonts/Roboto-700/Roboto-700.woff2 | Bin .../fonts/Roboto-700italic/LICENSE.txt | 0 .../Roboto-700italic/Roboto-700italic.eot | Bin .../Roboto-700italic/Roboto-700italic.svg | 0 .../Roboto-700italic/Roboto-700italic.ttf | Bin .../Roboto-700italic/Roboto-700italic.woff | Bin .../Roboto-700italic/Roboto-700italic.woff2 | Bin .../fonts/Roboto-italic/LICENSE.txt | 0 .../fonts/Roboto-italic/Roboto-italic.eot | Bin .../fonts/Roboto-italic/Roboto-italic.svg | 0 .../fonts/Roboto-italic/Roboto-italic.ttf | Bin .../fonts/Roboto-italic/Roboto-italic.woff | Bin .../fonts/Roboto-italic/Roboto-italic.woff2 | Bin .../fonts/Roboto-regular/LICENSE.txt | 0 .../fonts/Roboto-regular/Roboto-regular.eot | Bin .../fonts/Roboto-regular/Roboto-regular.svg | 0 .../fonts/Roboto-regular/Roboto-regular.ttf | Bin .../fonts/Roboto-regular/Roboto-regular.woff | Bin .../fonts/Roboto-regular/Roboto-regular.woff2 | Bin assets/{ => data}/fonts/roboto-slimfix.css | 0 assets/{ => data}/fonts/roboto.css | 0 assets/{ => data}/icons/README.md | 0 assets/{ => data}/icons/pm_dark_128.png | Bin assets/{ => data}/icons/pm_dark_256.png | Bin assets/{ => data}/icons/pm_dark_512.ico | Bin assets/{ => data}/icons/pm_dark_512.png | Bin assets/{ => data}/icons/pm_dark_blue_128.png | Bin assets/{ => data}/icons/pm_dark_blue_256.png | Bin assets/{ => data}/icons/pm_dark_blue_512.ico | Bin assets/{ => data}/icons/pm_dark_blue_512.png | Bin assets/{ => data}/icons/pm_dark_green_128.png | Bin assets/{ => data}/icons/pm_dark_green_256.png | Bin assets/{ => data}/icons/pm_dark_green_512.ico | Bin assets/{ => data}/icons/pm_dark_green_512.png | Bin assets/{ => data}/icons/pm_dark_red_128.png | Bin assets/{ => data}/icons/pm_dark_red_256.png | Bin assets/{ => data}/icons/pm_dark_red_512.ico | Bin assets/{ => data}/icons/pm_dark_red_512.png | Bin .../{ => data}/icons/pm_dark_yellow_128.png | Bin .../{ => data}/icons/pm_dark_yellow_256.png | Bin .../{ => data}/icons/pm_dark_yellow_512.ico | Bin .../{ => data}/icons/pm_dark_yellow_512.png | Bin assets/{ => data}/icons/pm_light_128.png | Bin assets/{ => data}/icons/pm_light_256.png | Bin assets/{ => data}/icons/pm_light_512.ico | Bin assets/{ => data}/icons/pm_light_512.png | Bin assets/{ => data}/icons/pm_light_blue_128.png | Bin assets/{ => data}/icons/pm_light_blue_256.png | Bin assets/{ => data}/icons/pm_light_blue_512.ico | Bin assets/{ => data}/icons/pm_light_blue_512.png | Bin .../{ => data}/icons/pm_light_green_128.png | Bin .../{ => data}/icons/pm_light_green_256.png | Bin .../{ => data}/icons/pm_light_green_512.ico | Bin .../{ => data}/icons/pm_light_green_512.png | Bin assets/{ => data}/icons/pm_light_red_128.png | Bin assets/{ => data}/icons/pm_light_red_256.png | Bin assets/{ => data}/icons/pm_light_red_512.ico | Bin assets/{ => data}/icons/pm_light_red_512.png | Bin .../{ => data}/icons/pm_light_yellow_128.png | Bin .../{ => data}/icons/pm_light_yellow_256.png | Bin .../{ => data}/icons/pm_light_yellow_512.ico | Bin .../{ => data}/icons/pm_light_yellow_512.png | Bin assets/{ => data}/img/Mobile.svg | 0 assets/{ => data}/img/flags/AD.png | Bin assets/{ => data}/img/flags/AE.png | Bin assets/{ => data}/img/flags/AF.png | Bin assets/{ => data}/img/flags/AG.png | Bin assets/{ => data}/img/flags/AI.png | Bin assets/{ => data}/img/flags/AL.png | Bin assets/{ => data}/img/flags/AM.png | Bin assets/{ => data}/img/flags/AN.png | Bin assets/{ => data}/img/flags/AO.png | Bin assets/{ => data}/img/flags/AQ.png | Bin assets/{ => data}/img/flags/AR.png | Bin assets/{ => data}/img/flags/AS.png | Bin assets/{ => data}/img/flags/AT.png | Bin assets/{ => data}/img/flags/AU.png | Bin assets/{ => data}/img/flags/AW.png | Bin assets/{ => data}/img/flags/AX.png | Bin assets/{ => data}/img/flags/AZ.png | Bin assets/{ => data}/img/flags/BA.png | Bin assets/{ => data}/img/flags/BB.png | Bin assets/{ => data}/img/flags/BD.png | Bin assets/{ => data}/img/flags/BE.png | Bin assets/{ => data}/img/flags/BF.png | Bin assets/{ => data}/img/flags/BG.png | Bin assets/{ => data}/img/flags/BH.png | Bin assets/{ => data}/img/flags/BI.png | Bin assets/{ => data}/img/flags/BJ.png | Bin assets/{ => data}/img/flags/BL.png | Bin assets/{ => data}/img/flags/BM.png | Bin assets/{ => data}/img/flags/BN.png | Bin assets/{ => data}/img/flags/BO.png | Bin assets/{ => data}/img/flags/BR.png | Bin assets/{ => data}/img/flags/BS.png | Bin assets/{ => data}/img/flags/BT.png | Bin assets/{ => data}/img/flags/BW.png | Bin assets/{ => data}/img/flags/BY.png | Bin assets/{ => data}/img/flags/BZ.png | Bin assets/{ => data}/img/flags/CA.png | Bin assets/{ => data}/img/flags/CC.png | Bin assets/{ => data}/img/flags/CD.png | Bin assets/{ => data}/img/flags/CF.png | Bin assets/{ => data}/img/flags/CG.png | Bin assets/{ => data}/img/flags/CH.png | Bin assets/{ => data}/img/flags/CI.png | Bin assets/{ => data}/img/flags/CK.png | Bin assets/{ => data}/img/flags/CL.png | Bin assets/{ => data}/img/flags/CM.png | Bin assets/{ => data}/img/flags/CN.png | Bin assets/{ => data}/img/flags/CO.png | Bin assets/{ => data}/img/flags/CR.png | Bin assets/{ => data}/img/flags/CT.png | Bin assets/{ => data}/img/flags/CU.png | Bin assets/{ => data}/img/flags/CV.png | Bin assets/{ => data}/img/flags/CW.png | Bin assets/{ => data}/img/flags/CX.png | Bin assets/{ => data}/img/flags/CY.png | Bin assets/{ => data}/img/flags/CZ.png | Bin assets/{ => data}/img/flags/DE.png | Bin assets/{ => data}/img/flags/DJ.png | Bin assets/{ => data}/img/flags/DK.png | Bin assets/{ => data}/img/flags/DM.png | Bin assets/{ => data}/img/flags/DO.png | Bin assets/{ => data}/img/flags/DZ.png | Bin assets/{ => data}/img/flags/EC.png | Bin assets/{ => data}/img/flags/EE.png | Bin assets/{ => data}/img/flags/EG.png | Bin assets/{ => data}/img/flags/EH.png | Bin assets/{ => data}/img/flags/ER.png | Bin assets/{ => data}/img/flags/ES.png | Bin assets/{ => data}/img/flags/ET.png | Bin assets/{ => data}/img/flags/EU.png | Bin assets/{ => data}/img/flags/FI.png | Bin assets/{ => data}/img/flags/FJ.png | Bin assets/{ => data}/img/flags/FK.png | Bin assets/{ => data}/img/flags/FM.png | Bin assets/{ => data}/img/flags/FO.png | Bin assets/{ => data}/img/flags/FR.png | Bin assets/{ => data}/img/flags/GA.png | Bin assets/{ => data}/img/flags/GB.png | Bin assets/{ => data}/img/flags/GD.png | Bin assets/{ => data}/img/flags/GE.png | Bin assets/{ => data}/img/flags/GG.png | Bin assets/{ => data}/img/flags/GH.png | Bin assets/{ => data}/img/flags/GI.png | Bin assets/{ => data}/img/flags/GL.png | Bin assets/{ => data}/img/flags/GM.png | Bin assets/{ => data}/img/flags/GN.png | Bin assets/{ => data}/img/flags/GQ.png | Bin assets/{ => data}/img/flags/GR.png | Bin assets/{ => data}/img/flags/GS.png | Bin assets/{ => data}/img/flags/GT.png | Bin assets/{ => data}/img/flags/GU.png | Bin assets/{ => data}/img/flags/GW.png | Bin assets/{ => data}/img/flags/GY.png | Bin assets/{ => data}/img/flags/HK.png | Bin assets/{ => data}/img/flags/HN.png | Bin assets/{ => data}/img/flags/HR.png | Bin assets/{ => data}/img/flags/HT.png | Bin assets/{ => data}/img/flags/HU.png | Bin assets/{ => data}/img/flags/IC.png | Bin assets/{ => data}/img/flags/ID.png | Bin assets/{ => data}/img/flags/IE.png | Bin assets/{ => data}/img/flags/IL.png | Bin assets/{ => data}/img/flags/IM.png | Bin assets/{ => data}/img/flags/IN.png | Bin assets/{ => data}/img/flags/IQ.png | Bin assets/{ => data}/img/flags/IR.png | Bin assets/{ => data}/img/flags/IS.png | Bin assets/{ => data}/img/flags/IT.png | Bin assets/{ => data}/img/flags/JE.png | Bin assets/{ => data}/img/flags/JM.png | Bin assets/{ => data}/img/flags/JO.png | Bin assets/{ => data}/img/flags/JP.png | Bin assets/{ => data}/img/flags/KE.png | Bin assets/{ => data}/img/flags/KG.png | Bin assets/{ => data}/img/flags/KH.png | Bin assets/{ => data}/img/flags/KI.png | Bin assets/{ => data}/img/flags/KM.png | Bin assets/{ => data}/img/flags/KN.png | Bin assets/{ => data}/img/flags/KP.png | Bin assets/{ => data}/img/flags/KR.png | Bin assets/{ => data}/img/flags/KW.png | Bin assets/{ => data}/img/flags/KY.png | Bin assets/{ => data}/img/flags/KZ.png | Bin assets/{ => data}/img/flags/LA.png | Bin assets/{ => data}/img/flags/LB.png | Bin assets/{ => data}/img/flags/LC.png | Bin assets/{ => data}/img/flags/LI.png | Bin assets/{ => data}/img/flags/LICENSE.txt | 0 assets/{ => data}/img/flags/LK.png | Bin assets/{ => data}/img/flags/LR.png | Bin assets/{ => data}/img/flags/LS.png | Bin assets/{ => data}/img/flags/LT.png | Bin assets/{ => data}/img/flags/LU.png | Bin assets/{ => data}/img/flags/LV.png | Bin assets/{ => data}/img/flags/LY.png | Bin assets/{ => data}/img/flags/MA.png | Bin assets/{ => data}/img/flags/MC.png | Bin assets/{ => data}/img/flags/MD.png | Bin assets/{ => data}/img/flags/ME.png | Bin assets/{ => data}/img/flags/MF.png | Bin assets/{ => data}/img/flags/MG.png | Bin assets/{ => data}/img/flags/MH.png | Bin assets/{ => data}/img/flags/MK.png | Bin assets/{ => data}/img/flags/ML.png | Bin assets/{ => data}/img/flags/MM.png | Bin assets/{ => data}/img/flags/MN.png | Bin assets/{ => data}/img/flags/MO.png | Bin assets/{ => data}/img/flags/MP.png | Bin assets/{ => data}/img/flags/MQ.png | Bin assets/{ => data}/img/flags/MR.png | Bin assets/{ => data}/img/flags/MS.png | Bin assets/{ => data}/img/flags/MT.png | Bin assets/{ => data}/img/flags/MU.png | Bin assets/{ => data}/img/flags/MV.png | Bin assets/{ => data}/img/flags/MW.png | Bin assets/{ => data}/img/flags/MX.png | Bin assets/{ => data}/img/flags/MY.png | Bin assets/{ => data}/img/flags/MZ.png | Bin assets/{ => data}/img/flags/NA.png | Bin assets/{ => data}/img/flags/NC.png | Bin assets/{ => data}/img/flags/NE.png | Bin assets/{ => data}/img/flags/NF.png | Bin assets/{ => data}/img/flags/NG.png | Bin assets/{ => data}/img/flags/NI.png | Bin assets/{ => data}/img/flags/NL.png | Bin assets/{ => data}/img/flags/NO.png | Bin assets/{ => data}/img/flags/NP.png | Bin assets/{ => data}/img/flags/NR.png | Bin assets/{ => data}/img/flags/NU.png | Bin assets/{ => data}/img/flags/NZ.png | Bin assets/{ => data}/img/flags/OM.png | Bin assets/{ => data}/img/flags/PA.png | Bin assets/{ => data}/img/flags/PE.png | Bin assets/{ => data}/img/flags/PF.png | Bin assets/{ => data}/img/flags/PG.png | Bin assets/{ => data}/img/flags/PH.png | Bin assets/{ => data}/img/flags/PK.png | Bin assets/{ => data}/img/flags/PL.png | Bin assets/{ => data}/img/flags/PN.png | Bin assets/{ => data}/img/flags/PR.png | Bin assets/{ => data}/img/flags/PS.png | Bin assets/{ => data}/img/flags/PT.png | Bin assets/{ => data}/img/flags/PW.png | Bin assets/{ => data}/img/flags/PY.png | Bin assets/{ => data}/img/flags/QA.png | Bin assets/{ => data}/img/flags/RE.png | Bin assets/{ => data}/img/flags/RO.png | Bin assets/{ => data}/img/flags/RS.png | Bin assets/{ => data}/img/flags/RU.png | Bin assets/{ => data}/img/flags/RW.png | Bin assets/{ => data}/img/flags/SA.png | Bin assets/{ => data}/img/flags/SB.png | Bin assets/{ => data}/img/flags/SC.png | Bin assets/{ => data}/img/flags/SD.png | Bin assets/{ => data}/img/flags/SE.png | Bin assets/{ => data}/img/flags/SG.png | Bin assets/{ => data}/img/flags/SH.png | Bin assets/{ => data}/img/flags/SI.png | Bin assets/{ => data}/img/flags/SK.png | Bin assets/{ => data}/img/flags/SL.png | Bin assets/{ => data}/img/flags/SM.png | Bin assets/{ => data}/img/flags/SN.png | Bin assets/{ => data}/img/flags/SO.png | Bin assets/{ => data}/img/flags/SR.png | Bin assets/{ => data}/img/flags/SS.png | Bin assets/{ => data}/img/flags/ST.png | Bin assets/{ => data}/img/flags/SV.png | Bin assets/{ => data}/img/flags/SX.png | Bin assets/{ => data}/img/flags/SY.png | Bin assets/{ => data}/img/flags/SZ.png | Bin assets/{ => data}/img/flags/TC.png | Bin assets/{ => data}/img/flags/TD.png | Bin assets/{ => data}/img/flags/TF.png | Bin assets/{ => data}/img/flags/TG.png | Bin assets/{ => data}/img/flags/TH.png | Bin assets/{ => data}/img/flags/TJ.png | Bin assets/{ => data}/img/flags/TK.png | Bin assets/{ => data}/img/flags/TL.png | Bin assets/{ => data}/img/flags/TM.png | Bin assets/{ => data}/img/flags/TN.png | Bin assets/{ => data}/img/flags/TO.png | Bin assets/{ => data}/img/flags/TR.png | Bin assets/{ => data}/img/flags/TT.png | Bin assets/{ => data}/img/flags/TV.png | Bin assets/{ => data}/img/flags/TW.png | Bin assets/{ => data}/img/flags/TZ.png | Bin assets/{ => data}/img/flags/UA.png | Bin assets/{ => data}/img/flags/UG.png | Bin assets/{ => data}/img/flags/US.png | Bin assets/{ => data}/img/flags/UY.png | Bin assets/{ => data}/img/flags/UZ.png | Bin assets/{ => data}/img/flags/VA.png | Bin assets/{ => data}/img/flags/VC.png | Bin assets/{ => data}/img/flags/VE.png | Bin assets/{ => data}/img/flags/VG.png | Bin assets/{ => data}/img/flags/VI.png | Bin assets/{ => data}/img/flags/VN.png | Bin assets/{ => data}/img/flags/VU.png | Bin assets/{ => data}/img/flags/WF.png | Bin assets/{ => data}/img/flags/WS.png | Bin assets/{ => data}/img/flags/YE.png | Bin assets/{ => data}/img/flags/YT.png | Bin assets/{ => data}/img/flags/ZA.png | Bin assets/{ => data}/img/flags/ZM.png | Bin assets/{ => data}/img/flags/ZW.png | Bin assets/{ => data}/img/flags/_abkhazia.png | Bin .../{ => data}/img/flags/_basque-country.png | Bin .../flags/_british-antarctic-territory.png | Bin assets/{ => data}/img/flags/_commonwealth.png | Bin assets/{ => data}/img/flags/_england.png | Bin assets/{ => data}/img/flags/_gosquared.png | Bin assets/{ => data}/img/flags/_kosovo.png | Bin assets/{ => data}/img/flags/_mars.png | Bin .../img/flags/_nagorno-karabakh.png | Bin assets/{ => data}/img/flags/_nato.png | Bin .../{ => data}/img/flags/_northern-cyprus.png | Bin assets/{ => data}/img/flags/_olympics.png | Bin assets/{ => data}/img/flags/_red-cross.png | Bin assets/{ => data}/img/flags/_scotland.png | Bin assets/{ => data}/img/flags/_somaliland.png | Bin .../{ => data}/img/flags/_south-ossetia.png | Bin .../{ => data}/img/flags/_united-nations.png | Bin assets/{ => data}/img/flags/_unknown.png | Bin assets/{ => data}/img/flags/_wales.png | Bin assets/{ => data}/img/linux.svg | 0 assets/{ => data}/img/mac.svg | 0 assets/{ => data}/img/plants1-br.png | Bin assets/{ => data}/img/plants1.png | Bin .../access-regional-content-easily.png | Bin .../built-from-the-ground-up.png | Bin .../img/spn-feature-carousel/bye-bye-vpns.png | Bin .../easily-control-your-privacy.png | Bin .../multiple-identities-for-each-app.png | Bin assets/{ => data}/img/spn-login.png | Bin assets/{ => data}/img/windows.svg | 0 assets/{ => data}/world-50m.json | 0 assets/icons.go | 8 + assets/icons_default.go | 102 +++++++ assets/icons_windows.go | 41 +++ cmds/notifier/.gitignore | 34 +++ cmds/notifier/README.md | 5 + cmds/notifier/http_api.go | 65 ++++ cmds/notifier/icons.go | 25 ++ cmds/notifier/main.go | 286 ++++++++++++++++++ cmds/notifier/notification.go | 36 +++ cmds/notifier/notify.go | 103 +++++++ cmds/notifier/notify_linux.go | 154 ++++++++++ cmds/notifier/notify_windows.go | 184 +++++++++++ cmds/notifier/shutdown.go | 50 +++ cmds/notifier/snoretoast-guid.patch | 15 + cmds/notifier/spn.go | 103 +++++++ cmds/notifier/subsystems.go | 122 ++++++++ cmds/notifier/tray.go | 218 +++++++++++++ .../notifier/wintoast/notification_builder.go | 90 ++++++ cmds/notifier/wintoast/wintoast.go | 217 +++++++++++++ desktop/angular/assets | 2 +- go.mod | 5 +- go.sum | 8 + runtime/.gitkeep | 1 - 392 files changed, 1879 insertions(+), 4 deletions(-) rename assets/{ => data}/fonts/Roboto-300/LICENSE.txt (100%) rename assets/{ => data}/fonts/Roboto-300/Roboto-300.eot (100%) rename assets/{ => data}/fonts/Roboto-300/Roboto-300.svg (100%) rename assets/{ => data}/fonts/Roboto-300/Roboto-300.ttf (100%) rename assets/{ => data}/fonts/Roboto-300/Roboto-300.woff (100%) rename assets/{ => data}/fonts/Roboto-300/Roboto-300.woff2 (100%) rename assets/{ => data}/fonts/Roboto-300italic/LICENSE.txt (100%) rename assets/{ => data}/fonts/Roboto-300italic/Roboto-300italic.eot (100%) rename assets/{ => data}/fonts/Roboto-300italic/Roboto-300italic.svg (100%) rename assets/{ => data}/fonts/Roboto-300italic/Roboto-300italic.ttf (100%) rename assets/{ => data}/fonts/Roboto-300italic/Roboto-300italic.woff (100%) rename assets/{ => data}/fonts/Roboto-300italic/Roboto-300italic.woff2 (100%) rename assets/{ => data}/fonts/Roboto-500/LICENSE.txt (100%) rename assets/{ => data}/fonts/Roboto-500/Roboto-500.eot (100%) rename assets/{ => data}/fonts/Roboto-500/Roboto-500.svg (100%) rename assets/{ => data}/fonts/Roboto-500/Roboto-500.ttf (100%) rename assets/{ => data}/fonts/Roboto-500/Roboto-500.woff (100%) rename assets/{ => data}/fonts/Roboto-500/Roboto-500.woff2 (100%) rename assets/{ => data}/fonts/Roboto-500italic/LICENSE.txt (100%) rename assets/{ => data}/fonts/Roboto-500italic/Roboto-500italic.eot (100%) rename assets/{ => data}/fonts/Roboto-500italic/Roboto-500italic.svg (100%) rename assets/{ => data}/fonts/Roboto-500italic/Roboto-500italic.ttf (100%) rename assets/{ => data}/fonts/Roboto-500italic/Roboto-500italic.woff (100%) rename assets/{ => data}/fonts/Roboto-500italic/Roboto-500italic.woff2 (100%) rename assets/{ => data}/fonts/Roboto-700/LICENSE.txt (100%) rename assets/{ => data}/fonts/Roboto-700/Roboto-700.eot (100%) rename assets/{ => data}/fonts/Roboto-700/Roboto-700.svg (100%) rename assets/{ => data}/fonts/Roboto-700/Roboto-700.ttf (100%) rename assets/{ => data}/fonts/Roboto-700/Roboto-700.woff (100%) rename assets/{ => data}/fonts/Roboto-700/Roboto-700.woff2 (100%) rename assets/{ => data}/fonts/Roboto-700italic/LICENSE.txt (100%) rename assets/{ => data}/fonts/Roboto-700italic/Roboto-700italic.eot (100%) rename assets/{ => data}/fonts/Roboto-700italic/Roboto-700italic.svg (100%) rename assets/{ => data}/fonts/Roboto-700italic/Roboto-700italic.ttf (100%) rename assets/{ => data}/fonts/Roboto-700italic/Roboto-700italic.woff (100%) rename assets/{ => data}/fonts/Roboto-700italic/Roboto-700italic.woff2 (100%) rename assets/{ => data}/fonts/Roboto-italic/LICENSE.txt (100%) rename assets/{ => data}/fonts/Roboto-italic/Roboto-italic.eot (100%) rename assets/{ => data}/fonts/Roboto-italic/Roboto-italic.svg (100%) rename assets/{ => data}/fonts/Roboto-italic/Roboto-italic.ttf (100%) rename assets/{ => data}/fonts/Roboto-italic/Roboto-italic.woff (100%) rename assets/{ => data}/fonts/Roboto-italic/Roboto-italic.woff2 (100%) rename assets/{ => data}/fonts/Roboto-regular/LICENSE.txt (100%) rename assets/{ => data}/fonts/Roboto-regular/Roboto-regular.eot (100%) rename assets/{ => data}/fonts/Roboto-regular/Roboto-regular.svg (100%) rename assets/{ => data}/fonts/Roboto-regular/Roboto-regular.ttf (100%) rename assets/{ => data}/fonts/Roboto-regular/Roboto-regular.woff (100%) rename assets/{ => data}/fonts/Roboto-regular/Roboto-regular.woff2 (100%) rename assets/{ => data}/fonts/roboto-slimfix.css (100%) rename assets/{ => data}/fonts/roboto.css (100%) rename assets/{ => data}/icons/README.md (100%) rename assets/{ => data}/icons/pm_dark_128.png (100%) rename assets/{ => data}/icons/pm_dark_256.png (100%) rename assets/{ => data}/icons/pm_dark_512.ico (100%) rename assets/{ => data}/icons/pm_dark_512.png (100%) rename assets/{ => data}/icons/pm_dark_blue_128.png (100%) rename assets/{ => data}/icons/pm_dark_blue_256.png (100%) rename assets/{ => data}/icons/pm_dark_blue_512.ico (100%) rename assets/{ => data}/icons/pm_dark_blue_512.png (100%) rename assets/{ => data}/icons/pm_dark_green_128.png (100%) rename assets/{ => data}/icons/pm_dark_green_256.png (100%) rename assets/{ => data}/icons/pm_dark_green_512.ico (100%) rename assets/{ => data}/icons/pm_dark_green_512.png (100%) rename assets/{ => data}/icons/pm_dark_red_128.png (100%) rename assets/{ => data}/icons/pm_dark_red_256.png (100%) rename assets/{ => data}/icons/pm_dark_red_512.ico (100%) rename assets/{ => data}/icons/pm_dark_red_512.png (100%) rename assets/{ => data}/icons/pm_dark_yellow_128.png (100%) rename assets/{ => data}/icons/pm_dark_yellow_256.png (100%) rename assets/{ => data}/icons/pm_dark_yellow_512.ico (100%) rename assets/{ => data}/icons/pm_dark_yellow_512.png (100%) rename assets/{ => data}/icons/pm_light_128.png (100%) rename assets/{ => data}/icons/pm_light_256.png (100%) rename assets/{ => data}/icons/pm_light_512.ico (100%) rename assets/{ => data}/icons/pm_light_512.png (100%) rename assets/{ => data}/icons/pm_light_blue_128.png (100%) rename assets/{ => data}/icons/pm_light_blue_256.png (100%) rename assets/{ => data}/icons/pm_light_blue_512.ico (100%) rename assets/{ => data}/icons/pm_light_blue_512.png (100%) rename assets/{ => data}/icons/pm_light_green_128.png (100%) rename assets/{ => data}/icons/pm_light_green_256.png (100%) rename assets/{ => data}/icons/pm_light_green_512.ico (100%) rename assets/{ => data}/icons/pm_light_green_512.png (100%) rename assets/{ => data}/icons/pm_light_red_128.png (100%) rename assets/{ => data}/icons/pm_light_red_256.png (100%) rename assets/{ => data}/icons/pm_light_red_512.ico (100%) rename assets/{ => data}/icons/pm_light_red_512.png (100%) rename assets/{ => data}/icons/pm_light_yellow_128.png (100%) rename assets/{ => data}/icons/pm_light_yellow_256.png (100%) rename assets/{ => data}/icons/pm_light_yellow_512.ico (100%) rename assets/{ => data}/icons/pm_light_yellow_512.png (100%) rename assets/{ => data}/img/Mobile.svg (100%) rename assets/{ => data}/img/flags/AD.png (100%) rename assets/{ => data}/img/flags/AE.png (100%) rename assets/{ => data}/img/flags/AF.png (100%) rename assets/{ => data}/img/flags/AG.png (100%) rename assets/{ => data}/img/flags/AI.png (100%) rename assets/{ => data}/img/flags/AL.png (100%) rename assets/{ => data}/img/flags/AM.png (100%) rename assets/{ => data}/img/flags/AN.png (100%) rename assets/{ => data}/img/flags/AO.png (100%) rename assets/{ => data}/img/flags/AQ.png (100%) rename assets/{ => data}/img/flags/AR.png (100%) rename assets/{ => data}/img/flags/AS.png (100%) rename assets/{ => data}/img/flags/AT.png (100%) rename assets/{ => data}/img/flags/AU.png (100%) rename assets/{ => data}/img/flags/AW.png (100%) rename assets/{ => data}/img/flags/AX.png (100%) rename assets/{ => data}/img/flags/AZ.png (100%) rename assets/{ => data}/img/flags/BA.png (100%) rename assets/{ => data}/img/flags/BB.png (100%) rename assets/{ => data}/img/flags/BD.png (100%) rename assets/{ => data}/img/flags/BE.png (100%) rename assets/{ => data}/img/flags/BF.png (100%) rename assets/{ => data}/img/flags/BG.png (100%) rename assets/{ => data}/img/flags/BH.png (100%) rename assets/{ => data}/img/flags/BI.png (100%) rename assets/{ => data}/img/flags/BJ.png (100%) rename assets/{ => data}/img/flags/BL.png (100%) rename assets/{ => data}/img/flags/BM.png (100%) rename assets/{ => data}/img/flags/BN.png (100%) rename assets/{ => data}/img/flags/BO.png (100%) rename assets/{ => data}/img/flags/BR.png (100%) rename assets/{ => data}/img/flags/BS.png (100%) rename assets/{ => data}/img/flags/BT.png (100%) rename assets/{ => data}/img/flags/BW.png (100%) rename assets/{ => data}/img/flags/BY.png (100%) rename assets/{ => data}/img/flags/BZ.png (100%) rename assets/{ => data}/img/flags/CA.png (100%) rename assets/{ => data}/img/flags/CC.png (100%) rename assets/{ => data}/img/flags/CD.png (100%) rename assets/{ => data}/img/flags/CF.png (100%) rename assets/{ => data}/img/flags/CG.png (100%) rename assets/{ => data}/img/flags/CH.png (100%) rename assets/{ => data}/img/flags/CI.png (100%) rename assets/{ => data}/img/flags/CK.png (100%) rename assets/{ => data}/img/flags/CL.png (100%) rename assets/{ => data}/img/flags/CM.png (100%) rename assets/{ => data}/img/flags/CN.png (100%) rename assets/{ => data}/img/flags/CO.png (100%) rename assets/{ => data}/img/flags/CR.png (100%) rename assets/{ => data}/img/flags/CT.png (100%) rename assets/{ => data}/img/flags/CU.png (100%) rename assets/{ => data}/img/flags/CV.png (100%) rename assets/{ => data}/img/flags/CW.png (100%) rename assets/{ => data}/img/flags/CX.png (100%) rename assets/{ => data}/img/flags/CY.png (100%) rename assets/{ => data}/img/flags/CZ.png (100%) rename assets/{ => data}/img/flags/DE.png (100%) rename assets/{ => data}/img/flags/DJ.png (100%) rename assets/{ => data}/img/flags/DK.png (100%) rename assets/{ => data}/img/flags/DM.png (100%) rename assets/{ => data}/img/flags/DO.png (100%) rename assets/{ => data}/img/flags/DZ.png (100%) rename assets/{ => data}/img/flags/EC.png (100%) rename assets/{ => data}/img/flags/EE.png (100%) rename assets/{ => data}/img/flags/EG.png (100%) rename assets/{ => data}/img/flags/EH.png (100%) rename assets/{ => data}/img/flags/ER.png (100%) rename assets/{ => data}/img/flags/ES.png (100%) rename assets/{ => data}/img/flags/ET.png (100%) rename assets/{ => data}/img/flags/EU.png (100%) rename assets/{ => data}/img/flags/FI.png (100%) rename assets/{ => data}/img/flags/FJ.png (100%) rename assets/{ => data}/img/flags/FK.png (100%) rename assets/{ => data}/img/flags/FM.png (100%) rename assets/{ => data}/img/flags/FO.png (100%) rename assets/{ => data}/img/flags/FR.png (100%) rename assets/{ => data}/img/flags/GA.png (100%) rename assets/{ => data}/img/flags/GB.png (100%) rename assets/{ => data}/img/flags/GD.png (100%) rename assets/{ => data}/img/flags/GE.png (100%) rename assets/{ => data}/img/flags/GG.png (100%) rename assets/{ => data}/img/flags/GH.png (100%) rename assets/{ => data}/img/flags/GI.png (100%) rename assets/{ => data}/img/flags/GL.png (100%) rename assets/{ => data}/img/flags/GM.png (100%) rename assets/{ => data}/img/flags/GN.png (100%) rename assets/{ => data}/img/flags/GQ.png (100%) rename assets/{ => data}/img/flags/GR.png (100%) rename assets/{ => data}/img/flags/GS.png (100%) rename assets/{ => data}/img/flags/GT.png (100%) rename assets/{ => data}/img/flags/GU.png (100%) rename assets/{ => data}/img/flags/GW.png (100%) rename assets/{ => data}/img/flags/GY.png (100%) rename assets/{ => data}/img/flags/HK.png (100%) rename assets/{ => data}/img/flags/HN.png (100%) rename assets/{ => data}/img/flags/HR.png (100%) rename assets/{ => data}/img/flags/HT.png (100%) rename assets/{ => data}/img/flags/HU.png (100%) rename assets/{ => data}/img/flags/IC.png (100%) rename assets/{ => data}/img/flags/ID.png (100%) rename assets/{ => data}/img/flags/IE.png (100%) rename assets/{ => data}/img/flags/IL.png (100%) rename assets/{ => data}/img/flags/IM.png (100%) rename assets/{ => data}/img/flags/IN.png (100%) rename assets/{ => data}/img/flags/IQ.png (100%) rename assets/{ => data}/img/flags/IR.png (100%) rename assets/{ => data}/img/flags/IS.png (100%) rename assets/{ => data}/img/flags/IT.png (100%) rename assets/{ => data}/img/flags/JE.png (100%) rename assets/{ => data}/img/flags/JM.png (100%) rename assets/{ => data}/img/flags/JO.png (100%) rename assets/{ => data}/img/flags/JP.png (100%) rename assets/{ => data}/img/flags/KE.png (100%) rename assets/{ => data}/img/flags/KG.png (100%) rename assets/{ => data}/img/flags/KH.png (100%) rename assets/{ => data}/img/flags/KI.png (100%) rename assets/{ => data}/img/flags/KM.png (100%) rename assets/{ => data}/img/flags/KN.png (100%) rename assets/{ => data}/img/flags/KP.png (100%) rename assets/{ => data}/img/flags/KR.png (100%) rename assets/{ => data}/img/flags/KW.png (100%) rename assets/{ => data}/img/flags/KY.png (100%) rename assets/{ => data}/img/flags/KZ.png (100%) rename assets/{ => data}/img/flags/LA.png (100%) rename assets/{ => data}/img/flags/LB.png (100%) rename assets/{ => data}/img/flags/LC.png (100%) rename assets/{ => data}/img/flags/LI.png (100%) rename assets/{ => data}/img/flags/LICENSE.txt (100%) rename assets/{ => data}/img/flags/LK.png (100%) rename assets/{ => data}/img/flags/LR.png (100%) rename assets/{ => data}/img/flags/LS.png (100%) rename assets/{ => data}/img/flags/LT.png (100%) rename assets/{ => data}/img/flags/LU.png (100%) rename assets/{ => data}/img/flags/LV.png (100%) rename assets/{ => data}/img/flags/LY.png (100%) rename assets/{ => data}/img/flags/MA.png (100%) rename assets/{ => data}/img/flags/MC.png (100%) rename assets/{ => data}/img/flags/MD.png (100%) rename assets/{ => data}/img/flags/ME.png (100%) rename assets/{ => data}/img/flags/MF.png (100%) rename assets/{ => data}/img/flags/MG.png (100%) rename assets/{ => data}/img/flags/MH.png (100%) rename assets/{ => data}/img/flags/MK.png (100%) rename assets/{ => data}/img/flags/ML.png (100%) rename assets/{ => data}/img/flags/MM.png (100%) rename assets/{ => data}/img/flags/MN.png (100%) rename assets/{ => data}/img/flags/MO.png (100%) rename assets/{ => data}/img/flags/MP.png (100%) rename assets/{ => data}/img/flags/MQ.png (100%) rename assets/{ => data}/img/flags/MR.png (100%) rename assets/{ => data}/img/flags/MS.png (100%) rename assets/{ => data}/img/flags/MT.png (100%) rename assets/{ => data}/img/flags/MU.png (100%) rename assets/{ => data}/img/flags/MV.png (100%) rename assets/{ => data}/img/flags/MW.png (100%) rename assets/{ => data}/img/flags/MX.png (100%) rename assets/{ => data}/img/flags/MY.png (100%) rename assets/{ => data}/img/flags/MZ.png (100%) rename assets/{ => data}/img/flags/NA.png (100%) rename assets/{ => data}/img/flags/NC.png (100%) rename assets/{ => data}/img/flags/NE.png (100%) rename assets/{ => data}/img/flags/NF.png (100%) rename assets/{ => data}/img/flags/NG.png (100%) rename assets/{ => data}/img/flags/NI.png (100%) rename assets/{ => data}/img/flags/NL.png (100%) rename assets/{ => data}/img/flags/NO.png (100%) rename assets/{ => data}/img/flags/NP.png (100%) rename assets/{ => data}/img/flags/NR.png (100%) rename assets/{ => data}/img/flags/NU.png (100%) rename assets/{ => data}/img/flags/NZ.png (100%) rename assets/{ => data}/img/flags/OM.png (100%) rename assets/{ => data}/img/flags/PA.png (100%) rename assets/{ => data}/img/flags/PE.png (100%) rename assets/{ => data}/img/flags/PF.png (100%) rename assets/{ => data}/img/flags/PG.png (100%) rename assets/{ => data}/img/flags/PH.png (100%) rename assets/{ => data}/img/flags/PK.png (100%) rename assets/{ => data}/img/flags/PL.png (100%) rename assets/{ => data}/img/flags/PN.png (100%) rename assets/{ => data}/img/flags/PR.png (100%) rename assets/{ => data}/img/flags/PS.png (100%) rename assets/{ => data}/img/flags/PT.png (100%) rename assets/{ => data}/img/flags/PW.png (100%) rename assets/{ => data}/img/flags/PY.png (100%) rename assets/{ => data}/img/flags/QA.png (100%) rename assets/{ => data}/img/flags/RE.png (100%) rename assets/{ => data}/img/flags/RO.png (100%) rename assets/{ => data}/img/flags/RS.png (100%) rename assets/{ => data}/img/flags/RU.png (100%) rename assets/{ => data}/img/flags/RW.png (100%) rename assets/{ => data}/img/flags/SA.png (100%) rename assets/{ => data}/img/flags/SB.png (100%) rename assets/{ => data}/img/flags/SC.png (100%) rename assets/{ => data}/img/flags/SD.png (100%) rename assets/{ => data}/img/flags/SE.png (100%) rename assets/{ => data}/img/flags/SG.png (100%) rename assets/{ => data}/img/flags/SH.png (100%) rename assets/{ => data}/img/flags/SI.png (100%) rename assets/{ => data}/img/flags/SK.png (100%) rename assets/{ => data}/img/flags/SL.png (100%) rename assets/{ => data}/img/flags/SM.png (100%) rename assets/{ => data}/img/flags/SN.png (100%) rename assets/{ => data}/img/flags/SO.png (100%) rename assets/{ => data}/img/flags/SR.png (100%) rename assets/{ => data}/img/flags/SS.png (100%) rename assets/{ => data}/img/flags/ST.png (100%) rename assets/{ => data}/img/flags/SV.png (100%) rename assets/{ => data}/img/flags/SX.png (100%) rename assets/{ => data}/img/flags/SY.png (100%) rename assets/{ => data}/img/flags/SZ.png (100%) rename assets/{ => data}/img/flags/TC.png (100%) rename assets/{ => data}/img/flags/TD.png (100%) rename assets/{ => data}/img/flags/TF.png (100%) rename assets/{ => data}/img/flags/TG.png (100%) rename assets/{ => data}/img/flags/TH.png (100%) rename assets/{ => data}/img/flags/TJ.png (100%) rename assets/{ => data}/img/flags/TK.png (100%) rename assets/{ => data}/img/flags/TL.png (100%) rename assets/{ => data}/img/flags/TM.png (100%) rename assets/{ => data}/img/flags/TN.png (100%) rename assets/{ => data}/img/flags/TO.png (100%) rename assets/{ => data}/img/flags/TR.png (100%) rename assets/{ => data}/img/flags/TT.png (100%) rename assets/{ => data}/img/flags/TV.png (100%) rename assets/{ => data}/img/flags/TW.png (100%) rename assets/{ => data}/img/flags/TZ.png (100%) rename assets/{ => data}/img/flags/UA.png (100%) rename assets/{ => data}/img/flags/UG.png (100%) rename assets/{ => data}/img/flags/US.png (100%) rename assets/{ => data}/img/flags/UY.png (100%) rename assets/{ => data}/img/flags/UZ.png (100%) rename assets/{ => data}/img/flags/VA.png (100%) rename assets/{ => data}/img/flags/VC.png (100%) rename assets/{ => data}/img/flags/VE.png (100%) rename assets/{ => data}/img/flags/VG.png (100%) rename assets/{ => data}/img/flags/VI.png (100%) rename assets/{ => data}/img/flags/VN.png (100%) rename assets/{ => data}/img/flags/VU.png (100%) rename assets/{ => data}/img/flags/WF.png (100%) rename assets/{ => data}/img/flags/WS.png (100%) rename assets/{ => data}/img/flags/YE.png (100%) rename assets/{ => data}/img/flags/YT.png (100%) rename assets/{ => data}/img/flags/ZA.png (100%) rename assets/{ => data}/img/flags/ZM.png (100%) rename assets/{ => data}/img/flags/ZW.png (100%) rename assets/{ => data}/img/flags/_abkhazia.png (100%) rename assets/{ => data}/img/flags/_basque-country.png (100%) rename assets/{ => data}/img/flags/_british-antarctic-territory.png (100%) rename assets/{ => data}/img/flags/_commonwealth.png (100%) rename assets/{ => data}/img/flags/_england.png (100%) rename assets/{ => data}/img/flags/_gosquared.png (100%) rename assets/{ => data}/img/flags/_kosovo.png (100%) rename assets/{ => data}/img/flags/_mars.png (100%) rename assets/{ => data}/img/flags/_nagorno-karabakh.png (100%) rename assets/{ => data}/img/flags/_nato.png (100%) rename assets/{ => data}/img/flags/_northern-cyprus.png (100%) rename assets/{ => data}/img/flags/_olympics.png (100%) rename assets/{ => data}/img/flags/_red-cross.png (100%) rename assets/{ => data}/img/flags/_scotland.png (100%) rename assets/{ => data}/img/flags/_somaliland.png (100%) rename assets/{ => data}/img/flags/_south-ossetia.png (100%) rename assets/{ => data}/img/flags/_united-nations.png (100%) rename assets/{ => data}/img/flags/_unknown.png (100%) rename assets/{ => data}/img/flags/_wales.png (100%) rename assets/{ => data}/img/linux.svg (100%) rename assets/{ => data}/img/mac.svg (100%) rename assets/{ => data}/img/plants1-br.png (100%) rename assets/{ => data}/img/plants1.png (100%) rename assets/{ => data}/img/spn-feature-carousel/access-regional-content-easily.png (100%) rename assets/{ => data}/img/spn-feature-carousel/built-from-the-ground-up.png (100%) rename assets/{ => data}/img/spn-feature-carousel/bye-bye-vpns.png (100%) rename assets/{ => data}/img/spn-feature-carousel/easily-control-your-privacy.png (100%) rename assets/{ => data}/img/spn-feature-carousel/multiple-identities-for-each-app.png (100%) rename assets/{ => data}/img/spn-login.png (100%) rename assets/{ => data}/img/windows.svg (100%) rename assets/{ => data}/world-50m.json (100%) create mode 100644 assets/icons.go create mode 100644 assets/icons_default.go create mode 100644 assets/icons_windows.go create mode 100644 cmds/notifier/.gitignore create mode 100644 cmds/notifier/README.md create mode 100644 cmds/notifier/http_api.go create mode 100644 cmds/notifier/icons.go create mode 100644 cmds/notifier/main.go create mode 100644 cmds/notifier/notification.go create mode 100644 cmds/notifier/notify.go create mode 100644 cmds/notifier/notify_linux.go create mode 100644 cmds/notifier/notify_windows.go create mode 100644 cmds/notifier/shutdown.go create mode 100644 cmds/notifier/snoretoast-guid.patch create mode 100644 cmds/notifier/spn.go create mode 100644 cmds/notifier/subsystems.go create mode 100644 cmds/notifier/tray.go create mode 100644 cmds/notifier/wintoast/notification_builder.go create mode 100644 cmds/notifier/wintoast/wintoast.go delete mode 100644 runtime/.gitkeep diff --git a/Earthfile b/Earthfile index 3638f0d6..c0da3562 100644 --- a/Earthfile +++ b/Earthfile @@ -35,6 +35,13 @@ go-base: COPY service ./service COPY spn ./spn + # The cmds/notifier embeds some icons but go:embed is not allowed + # to leave the package directory so there's a small go-package in + # assets. Once we drop the notify in favor of the tauri replacement + # we can remove the following line and also remove all go-code from + # ./assets + COPY assets ./assets + # mod-tidy runs 'go mod tidy', saving go.mod and go.sum locally. mod-tidy: FROM +go-base @@ -140,7 +147,7 @@ angular-deps: COPY desktop/angular/package.json . COPY desktop/angular/package-lock.json . - COPY assets/ ./assets + COPY assets/data ./assets RUN npm install diff --git a/assets/fonts/Roboto-300/LICENSE.txt b/assets/data/fonts/Roboto-300/LICENSE.txt similarity index 100% rename from assets/fonts/Roboto-300/LICENSE.txt rename to assets/data/fonts/Roboto-300/LICENSE.txt diff --git a/assets/fonts/Roboto-300/Roboto-300.eot b/assets/data/fonts/Roboto-300/Roboto-300.eot similarity index 100% rename from assets/fonts/Roboto-300/Roboto-300.eot rename to assets/data/fonts/Roboto-300/Roboto-300.eot diff --git a/assets/fonts/Roboto-300/Roboto-300.svg b/assets/data/fonts/Roboto-300/Roboto-300.svg similarity index 100% rename from assets/fonts/Roboto-300/Roboto-300.svg rename to assets/data/fonts/Roboto-300/Roboto-300.svg diff --git a/assets/fonts/Roboto-300/Roboto-300.ttf b/assets/data/fonts/Roboto-300/Roboto-300.ttf similarity index 100% rename from assets/fonts/Roboto-300/Roboto-300.ttf rename to assets/data/fonts/Roboto-300/Roboto-300.ttf diff --git a/assets/fonts/Roboto-300/Roboto-300.woff b/assets/data/fonts/Roboto-300/Roboto-300.woff similarity index 100% rename from assets/fonts/Roboto-300/Roboto-300.woff rename to assets/data/fonts/Roboto-300/Roboto-300.woff diff --git a/assets/fonts/Roboto-300/Roboto-300.woff2 b/assets/data/fonts/Roboto-300/Roboto-300.woff2 similarity index 100% rename from assets/fonts/Roboto-300/Roboto-300.woff2 rename to assets/data/fonts/Roboto-300/Roboto-300.woff2 diff --git a/assets/fonts/Roboto-300italic/LICENSE.txt b/assets/data/fonts/Roboto-300italic/LICENSE.txt similarity index 100% rename from assets/fonts/Roboto-300italic/LICENSE.txt rename to assets/data/fonts/Roboto-300italic/LICENSE.txt diff --git a/assets/fonts/Roboto-300italic/Roboto-300italic.eot b/assets/data/fonts/Roboto-300italic/Roboto-300italic.eot similarity index 100% rename from assets/fonts/Roboto-300italic/Roboto-300italic.eot rename to assets/data/fonts/Roboto-300italic/Roboto-300italic.eot diff --git a/assets/fonts/Roboto-300italic/Roboto-300italic.svg b/assets/data/fonts/Roboto-300italic/Roboto-300italic.svg similarity index 100% rename from assets/fonts/Roboto-300italic/Roboto-300italic.svg rename to assets/data/fonts/Roboto-300italic/Roboto-300italic.svg diff --git a/assets/fonts/Roboto-300italic/Roboto-300italic.ttf b/assets/data/fonts/Roboto-300italic/Roboto-300italic.ttf similarity index 100% rename from assets/fonts/Roboto-300italic/Roboto-300italic.ttf rename to assets/data/fonts/Roboto-300italic/Roboto-300italic.ttf diff --git a/assets/fonts/Roboto-300italic/Roboto-300italic.woff b/assets/data/fonts/Roboto-300italic/Roboto-300italic.woff similarity index 100% rename from assets/fonts/Roboto-300italic/Roboto-300italic.woff rename to assets/data/fonts/Roboto-300italic/Roboto-300italic.woff diff --git a/assets/fonts/Roboto-300italic/Roboto-300italic.woff2 b/assets/data/fonts/Roboto-300italic/Roboto-300italic.woff2 similarity index 100% rename from assets/fonts/Roboto-300italic/Roboto-300italic.woff2 rename to assets/data/fonts/Roboto-300italic/Roboto-300italic.woff2 diff --git a/assets/fonts/Roboto-500/LICENSE.txt b/assets/data/fonts/Roboto-500/LICENSE.txt similarity index 100% rename from assets/fonts/Roboto-500/LICENSE.txt rename to assets/data/fonts/Roboto-500/LICENSE.txt diff --git a/assets/fonts/Roboto-500/Roboto-500.eot b/assets/data/fonts/Roboto-500/Roboto-500.eot similarity index 100% rename from assets/fonts/Roboto-500/Roboto-500.eot rename to assets/data/fonts/Roboto-500/Roboto-500.eot diff --git a/assets/fonts/Roboto-500/Roboto-500.svg b/assets/data/fonts/Roboto-500/Roboto-500.svg similarity index 100% rename from assets/fonts/Roboto-500/Roboto-500.svg rename to assets/data/fonts/Roboto-500/Roboto-500.svg diff --git a/assets/fonts/Roboto-500/Roboto-500.ttf b/assets/data/fonts/Roboto-500/Roboto-500.ttf similarity index 100% rename from assets/fonts/Roboto-500/Roboto-500.ttf rename to assets/data/fonts/Roboto-500/Roboto-500.ttf diff --git a/assets/fonts/Roboto-500/Roboto-500.woff b/assets/data/fonts/Roboto-500/Roboto-500.woff similarity index 100% rename from assets/fonts/Roboto-500/Roboto-500.woff rename to assets/data/fonts/Roboto-500/Roboto-500.woff diff --git a/assets/fonts/Roboto-500/Roboto-500.woff2 b/assets/data/fonts/Roboto-500/Roboto-500.woff2 similarity index 100% rename from assets/fonts/Roboto-500/Roboto-500.woff2 rename to assets/data/fonts/Roboto-500/Roboto-500.woff2 diff --git a/assets/fonts/Roboto-500italic/LICENSE.txt b/assets/data/fonts/Roboto-500italic/LICENSE.txt similarity index 100% rename from assets/fonts/Roboto-500italic/LICENSE.txt rename to assets/data/fonts/Roboto-500italic/LICENSE.txt diff --git a/assets/fonts/Roboto-500italic/Roboto-500italic.eot b/assets/data/fonts/Roboto-500italic/Roboto-500italic.eot similarity index 100% rename from assets/fonts/Roboto-500italic/Roboto-500italic.eot rename to assets/data/fonts/Roboto-500italic/Roboto-500italic.eot diff --git a/assets/fonts/Roboto-500italic/Roboto-500italic.svg b/assets/data/fonts/Roboto-500italic/Roboto-500italic.svg similarity index 100% rename from assets/fonts/Roboto-500italic/Roboto-500italic.svg rename to assets/data/fonts/Roboto-500italic/Roboto-500italic.svg diff --git a/assets/fonts/Roboto-500italic/Roboto-500italic.ttf b/assets/data/fonts/Roboto-500italic/Roboto-500italic.ttf similarity index 100% rename from assets/fonts/Roboto-500italic/Roboto-500italic.ttf rename to assets/data/fonts/Roboto-500italic/Roboto-500italic.ttf diff --git a/assets/fonts/Roboto-500italic/Roboto-500italic.woff b/assets/data/fonts/Roboto-500italic/Roboto-500italic.woff similarity index 100% rename from assets/fonts/Roboto-500italic/Roboto-500italic.woff rename to assets/data/fonts/Roboto-500italic/Roboto-500italic.woff diff --git a/assets/fonts/Roboto-500italic/Roboto-500italic.woff2 b/assets/data/fonts/Roboto-500italic/Roboto-500italic.woff2 similarity index 100% rename from assets/fonts/Roboto-500italic/Roboto-500italic.woff2 rename to assets/data/fonts/Roboto-500italic/Roboto-500italic.woff2 diff --git a/assets/fonts/Roboto-700/LICENSE.txt b/assets/data/fonts/Roboto-700/LICENSE.txt similarity index 100% rename from assets/fonts/Roboto-700/LICENSE.txt rename to assets/data/fonts/Roboto-700/LICENSE.txt diff --git a/assets/fonts/Roboto-700/Roboto-700.eot b/assets/data/fonts/Roboto-700/Roboto-700.eot similarity index 100% rename from assets/fonts/Roboto-700/Roboto-700.eot rename to assets/data/fonts/Roboto-700/Roboto-700.eot diff --git a/assets/fonts/Roboto-700/Roboto-700.svg b/assets/data/fonts/Roboto-700/Roboto-700.svg similarity index 100% rename from assets/fonts/Roboto-700/Roboto-700.svg rename to assets/data/fonts/Roboto-700/Roboto-700.svg diff --git a/assets/fonts/Roboto-700/Roboto-700.ttf b/assets/data/fonts/Roboto-700/Roboto-700.ttf similarity index 100% rename from assets/fonts/Roboto-700/Roboto-700.ttf rename to assets/data/fonts/Roboto-700/Roboto-700.ttf diff --git a/assets/fonts/Roboto-700/Roboto-700.woff b/assets/data/fonts/Roboto-700/Roboto-700.woff similarity index 100% rename from assets/fonts/Roboto-700/Roboto-700.woff rename to assets/data/fonts/Roboto-700/Roboto-700.woff diff --git a/assets/fonts/Roboto-700/Roboto-700.woff2 b/assets/data/fonts/Roboto-700/Roboto-700.woff2 similarity index 100% rename from assets/fonts/Roboto-700/Roboto-700.woff2 rename to assets/data/fonts/Roboto-700/Roboto-700.woff2 diff --git a/assets/fonts/Roboto-700italic/LICENSE.txt b/assets/data/fonts/Roboto-700italic/LICENSE.txt similarity index 100% rename from assets/fonts/Roboto-700italic/LICENSE.txt rename to assets/data/fonts/Roboto-700italic/LICENSE.txt diff --git a/assets/fonts/Roboto-700italic/Roboto-700italic.eot b/assets/data/fonts/Roboto-700italic/Roboto-700italic.eot similarity index 100% rename from assets/fonts/Roboto-700italic/Roboto-700italic.eot rename to assets/data/fonts/Roboto-700italic/Roboto-700italic.eot diff --git a/assets/fonts/Roboto-700italic/Roboto-700italic.svg b/assets/data/fonts/Roboto-700italic/Roboto-700italic.svg similarity index 100% rename from assets/fonts/Roboto-700italic/Roboto-700italic.svg rename to assets/data/fonts/Roboto-700italic/Roboto-700italic.svg diff --git a/assets/fonts/Roboto-700italic/Roboto-700italic.ttf b/assets/data/fonts/Roboto-700italic/Roboto-700italic.ttf similarity index 100% rename from assets/fonts/Roboto-700italic/Roboto-700italic.ttf rename to assets/data/fonts/Roboto-700italic/Roboto-700italic.ttf diff --git a/assets/fonts/Roboto-700italic/Roboto-700italic.woff b/assets/data/fonts/Roboto-700italic/Roboto-700italic.woff similarity index 100% rename from assets/fonts/Roboto-700italic/Roboto-700italic.woff rename to assets/data/fonts/Roboto-700italic/Roboto-700italic.woff diff --git a/assets/fonts/Roboto-700italic/Roboto-700italic.woff2 b/assets/data/fonts/Roboto-700italic/Roboto-700italic.woff2 similarity index 100% rename from assets/fonts/Roboto-700italic/Roboto-700italic.woff2 rename to assets/data/fonts/Roboto-700italic/Roboto-700italic.woff2 diff --git a/assets/fonts/Roboto-italic/LICENSE.txt b/assets/data/fonts/Roboto-italic/LICENSE.txt similarity index 100% rename from assets/fonts/Roboto-italic/LICENSE.txt rename to assets/data/fonts/Roboto-italic/LICENSE.txt diff --git a/assets/fonts/Roboto-italic/Roboto-italic.eot b/assets/data/fonts/Roboto-italic/Roboto-italic.eot similarity index 100% rename from assets/fonts/Roboto-italic/Roboto-italic.eot rename to assets/data/fonts/Roboto-italic/Roboto-italic.eot diff --git a/assets/fonts/Roboto-italic/Roboto-italic.svg b/assets/data/fonts/Roboto-italic/Roboto-italic.svg similarity index 100% rename from assets/fonts/Roboto-italic/Roboto-italic.svg rename to assets/data/fonts/Roboto-italic/Roboto-italic.svg diff --git a/assets/fonts/Roboto-italic/Roboto-italic.ttf b/assets/data/fonts/Roboto-italic/Roboto-italic.ttf similarity index 100% rename from assets/fonts/Roboto-italic/Roboto-italic.ttf rename to assets/data/fonts/Roboto-italic/Roboto-italic.ttf diff --git a/assets/fonts/Roboto-italic/Roboto-italic.woff b/assets/data/fonts/Roboto-italic/Roboto-italic.woff similarity index 100% rename from assets/fonts/Roboto-italic/Roboto-italic.woff rename to assets/data/fonts/Roboto-italic/Roboto-italic.woff diff --git a/assets/fonts/Roboto-italic/Roboto-italic.woff2 b/assets/data/fonts/Roboto-italic/Roboto-italic.woff2 similarity index 100% rename from assets/fonts/Roboto-italic/Roboto-italic.woff2 rename to assets/data/fonts/Roboto-italic/Roboto-italic.woff2 diff --git a/assets/fonts/Roboto-regular/LICENSE.txt b/assets/data/fonts/Roboto-regular/LICENSE.txt similarity index 100% rename from assets/fonts/Roboto-regular/LICENSE.txt rename to assets/data/fonts/Roboto-regular/LICENSE.txt diff --git a/assets/fonts/Roboto-regular/Roboto-regular.eot b/assets/data/fonts/Roboto-regular/Roboto-regular.eot similarity index 100% rename from assets/fonts/Roboto-regular/Roboto-regular.eot rename to assets/data/fonts/Roboto-regular/Roboto-regular.eot diff --git a/assets/fonts/Roboto-regular/Roboto-regular.svg b/assets/data/fonts/Roboto-regular/Roboto-regular.svg similarity index 100% rename from assets/fonts/Roboto-regular/Roboto-regular.svg rename to assets/data/fonts/Roboto-regular/Roboto-regular.svg diff --git a/assets/fonts/Roboto-regular/Roboto-regular.ttf b/assets/data/fonts/Roboto-regular/Roboto-regular.ttf similarity index 100% rename from assets/fonts/Roboto-regular/Roboto-regular.ttf rename to assets/data/fonts/Roboto-regular/Roboto-regular.ttf diff --git a/assets/fonts/Roboto-regular/Roboto-regular.woff b/assets/data/fonts/Roboto-regular/Roboto-regular.woff similarity index 100% rename from assets/fonts/Roboto-regular/Roboto-regular.woff rename to assets/data/fonts/Roboto-regular/Roboto-regular.woff diff --git a/assets/fonts/Roboto-regular/Roboto-regular.woff2 b/assets/data/fonts/Roboto-regular/Roboto-regular.woff2 similarity index 100% rename from assets/fonts/Roboto-regular/Roboto-regular.woff2 rename to assets/data/fonts/Roboto-regular/Roboto-regular.woff2 diff --git a/assets/fonts/roboto-slimfix.css b/assets/data/fonts/roboto-slimfix.css similarity index 100% rename from assets/fonts/roboto-slimfix.css rename to assets/data/fonts/roboto-slimfix.css diff --git a/assets/fonts/roboto.css b/assets/data/fonts/roboto.css similarity index 100% rename from assets/fonts/roboto.css rename to assets/data/fonts/roboto.css diff --git a/assets/icons/README.md b/assets/data/icons/README.md similarity index 100% rename from assets/icons/README.md rename to assets/data/icons/README.md diff --git a/assets/icons/pm_dark_128.png b/assets/data/icons/pm_dark_128.png similarity index 100% rename from assets/icons/pm_dark_128.png rename to assets/data/icons/pm_dark_128.png diff --git a/assets/icons/pm_dark_256.png b/assets/data/icons/pm_dark_256.png similarity index 100% rename from assets/icons/pm_dark_256.png rename to assets/data/icons/pm_dark_256.png diff --git a/assets/icons/pm_dark_512.ico b/assets/data/icons/pm_dark_512.ico similarity index 100% rename from assets/icons/pm_dark_512.ico rename to assets/data/icons/pm_dark_512.ico diff --git a/assets/icons/pm_dark_512.png b/assets/data/icons/pm_dark_512.png similarity index 100% rename from assets/icons/pm_dark_512.png rename to assets/data/icons/pm_dark_512.png diff --git a/assets/icons/pm_dark_blue_128.png b/assets/data/icons/pm_dark_blue_128.png similarity index 100% rename from assets/icons/pm_dark_blue_128.png rename to assets/data/icons/pm_dark_blue_128.png diff --git a/assets/icons/pm_dark_blue_256.png b/assets/data/icons/pm_dark_blue_256.png similarity index 100% rename from assets/icons/pm_dark_blue_256.png rename to assets/data/icons/pm_dark_blue_256.png diff --git a/assets/icons/pm_dark_blue_512.ico b/assets/data/icons/pm_dark_blue_512.ico similarity index 100% rename from assets/icons/pm_dark_blue_512.ico rename to assets/data/icons/pm_dark_blue_512.ico diff --git a/assets/icons/pm_dark_blue_512.png b/assets/data/icons/pm_dark_blue_512.png similarity index 100% rename from assets/icons/pm_dark_blue_512.png rename to assets/data/icons/pm_dark_blue_512.png diff --git a/assets/icons/pm_dark_green_128.png b/assets/data/icons/pm_dark_green_128.png similarity index 100% rename from assets/icons/pm_dark_green_128.png rename to assets/data/icons/pm_dark_green_128.png diff --git a/assets/icons/pm_dark_green_256.png b/assets/data/icons/pm_dark_green_256.png similarity index 100% rename from assets/icons/pm_dark_green_256.png rename to assets/data/icons/pm_dark_green_256.png diff --git a/assets/icons/pm_dark_green_512.ico b/assets/data/icons/pm_dark_green_512.ico similarity index 100% rename from assets/icons/pm_dark_green_512.ico rename to assets/data/icons/pm_dark_green_512.ico diff --git a/assets/icons/pm_dark_green_512.png b/assets/data/icons/pm_dark_green_512.png similarity index 100% rename from assets/icons/pm_dark_green_512.png rename to assets/data/icons/pm_dark_green_512.png diff --git a/assets/icons/pm_dark_red_128.png b/assets/data/icons/pm_dark_red_128.png similarity index 100% rename from assets/icons/pm_dark_red_128.png rename to assets/data/icons/pm_dark_red_128.png diff --git a/assets/icons/pm_dark_red_256.png b/assets/data/icons/pm_dark_red_256.png similarity index 100% rename from assets/icons/pm_dark_red_256.png rename to assets/data/icons/pm_dark_red_256.png diff --git a/assets/icons/pm_dark_red_512.ico b/assets/data/icons/pm_dark_red_512.ico similarity index 100% rename from assets/icons/pm_dark_red_512.ico rename to assets/data/icons/pm_dark_red_512.ico diff --git a/assets/icons/pm_dark_red_512.png b/assets/data/icons/pm_dark_red_512.png similarity index 100% rename from assets/icons/pm_dark_red_512.png rename to assets/data/icons/pm_dark_red_512.png diff --git a/assets/icons/pm_dark_yellow_128.png b/assets/data/icons/pm_dark_yellow_128.png similarity index 100% rename from assets/icons/pm_dark_yellow_128.png rename to assets/data/icons/pm_dark_yellow_128.png diff --git a/assets/icons/pm_dark_yellow_256.png b/assets/data/icons/pm_dark_yellow_256.png similarity index 100% rename from assets/icons/pm_dark_yellow_256.png rename to assets/data/icons/pm_dark_yellow_256.png diff --git a/assets/icons/pm_dark_yellow_512.ico b/assets/data/icons/pm_dark_yellow_512.ico similarity index 100% rename from assets/icons/pm_dark_yellow_512.ico rename to assets/data/icons/pm_dark_yellow_512.ico diff --git a/assets/icons/pm_dark_yellow_512.png b/assets/data/icons/pm_dark_yellow_512.png similarity index 100% rename from assets/icons/pm_dark_yellow_512.png rename to assets/data/icons/pm_dark_yellow_512.png diff --git a/assets/icons/pm_light_128.png b/assets/data/icons/pm_light_128.png similarity index 100% rename from assets/icons/pm_light_128.png rename to assets/data/icons/pm_light_128.png diff --git a/assets/icons/pm_light_256.png b/assets/data/icons/pm_light_256.png similarity index 100% rename from assets/icons/pm_light_256.png rename to assets/data/icons/pm_light_256.png diff --git a/assets/icons/pm_light_512.ico b/assets/data/icons/pm_light_512.ico similarity index 100% rename from assets/icons/pm_light_512.ico rename to assets/data/icons/pm_light_512.ico diff --git a/assets/icons/pm_light_512.png b/assets/data/icons/pm_light_512.png similarity index 100% rename from assets/icons/pm_light_512.png rename to assets/data/icons/pm_light_512.png diff --git a/assets/icons/pm_light_blue_128.png b/assets/data/icons/pm_light_blue_128.png similarity index 100% rename from assets/icons/pm_light_blue_128.png rename to assets/data/icons/pm_light_blue_128.png diff --git a/assets/icons/pm_light_blue_256.png b/assets/data/icons/pm_light_blue_256.png similarity index 100% rename from assets/icons/pm_light_blue_256.png rename to assets/data/icons/pm_light_blue_256.png diff --git a/assets/icons/pm_light_blue_512.ico b/assets/data/icons/pm_light_blue_512.ico similarity index 100% rename from assets/icons/pm_light_blue_512.ico rename to assets/data/icons/pm_light_blue_512.ico diff --git a/assets/icons/pm_light_blue_512.png b/assets/data/icons/pm_light_blue_512.png similarity index 100% rename from assets/icons/pm_light_blue_512.png rename to assets/data/icons/pm_light_blue_512.png diff --git a/assets/icons/pm_light_green_128.png b/assets/data/icons/pm_light_green_128.png similarity index 100% rename from assets/icons/pm_light_green_128.png rename to assets/data/icons/pm_light_green_128.png diff --git a/assets/icons/pm_light_green_256.png b/assets/data/icons/pm_light_green_256.png similarity index 100% rename from assets/icons/pm_light_green_256.png rename to assets/data/icons/pm_light_green_256.png diff --git a/assets/icons/pm_light_green_512.ico b/assets/data/icons/pm_light_green_512.ico similarity index 100% rename from assets/icons/pm_light_green_512.ico rename to assets/data/icons/pm_light_green_512.ico diff --git a/assets/icons/pm_light_green_512.png b/assets/data/icons/pm_light_green_512.png similarity index 100% rename from assets/icons/pm_light_green_512.png rename to assets/data/icons/pm_light_green_512.png diff --git a/assets/icons/pm_light_red_128.png b/assets/data/icons/pm_light_red_128.png similarity index 100% rename from assets/icons/pm_light_red_128.png rename to assets/data/icons/pm_light_red_128.png diff --git a/assets/icons/pm_light_red_256.png b/assets/data/icons/pm_light_red_256.png similarity index 100% rename from assets/icons/pm_light_red_256.png rename to assets/data/icons/pm_light_red_256.png diff --git a/assets/icons/pm_light_red_512.ico b/assets/data/icons/pm_light_red_512.ico similarity index 100% rename from assets/icons/pm_light_red_512.ico rename to assets/data/icons/pm_light_red_512.ico diff --git a/assets/icons/pm_light_red_512.png b/assets/data/icons/pm_light_red_512.png similarity index 100% rename from assets/icons/pm_light_red_512.png rename to assets/data/icons/pm_light_red_512.png diff --git a/assets/icons/pm_light_yellow_128.png b/assets/data/icons/pm_light_yellow_128.png similarity index 100% rename from assets/icons/pm_light_yellow_128.png rename to assets/data/icons/pm_light_yellow_128.png diff --git a/assets/icons/pm_light_yellow_256.png b/assets/data/icons/pm_light_yellow_256.png similarity index 100% rename from assets/icons/pm_light_yellow_256.png rename to assets/data/icons/pm_light_yellow_256.png diff --git a/assets/icons/pm_light_yellow_512.ico b/assets/data/icons/pm_light_yellow_512.ico similarity index 100% rename from assets/icons/pm_light_yellow_512.ico rename to assets/data/icons/pm_light_yellow_512.ico diff --git a/assets/icons/pm_light_yellow_512.png b/assets/data/icons/pm_light_yellow_512.png similarity index 100% rename from assets/icons/pm_light_yellow_512.png rename to assets/data/icons/pm_light_yellow_512.png diff --git a/assets/img/Mobile.svg b/assets/data/img/Mobile.svg similarity index 100% rename from assets/img/Mobile.svg rename to assets/data/img/Mobile.svg diff --git a/assets/img/flags/AD.png b/assets/data/img/flags/AD.png similarity index 100% rename from assets/img/flags/AD.png rename to assets/data/img/flags/AD.png diff --git a/assets/img/flags/AE.png b/assets/data/img/flags/AE.png similarity index 100% rename from assets/img/flags/AE.png rename to assets/data/img/flags/AE.png diff --git a/assets/img/flags/AF.png b/assets/data/img/flags/AF.png similarity index 100% rename from assets/img/flags/AF.png rename to assets/data/img/flags/AF.png diff --git a/assets/img/flags/AG.png b/assets/data/img/flags/AG.png similarity index 100% rename from assets/img/flags/AG.png rename to assets/data/img/flags/AG.png diff --git a/assets/img/flags/AI.png b/assets/data/img/flags/AI.png similarity index 100% rename from assets/img/flags/AI.png rename to assets/data/img/flags/AI.png diff --git a/assets/img/flags/AL.png b/assets/data/img/flags/AL.png similarity index 100% rename from assets/img/flags/AL.png rename to assets/data/img/flags/AL.png diff --git a/assets/img/flags/AM.png b/assets/data/img/flags/AM.png similarity index 100% rename from assets/img/flags/AM.png rename to assets/data/img/flags/AM.png diff --git a/assets/img/flags/AN.png b/assets/data/img/flags/AN.png similarity index 100% rename from assets/img/flags/AN.png rename to assets/data/img/flags/AN.png diff --git a/assets/img/flags/AO.png b/assets/data/img/flags/AO.png similarity index 100% rename from assets/img/flags/AO.png rename to assets/data/img/flags/AO.png diff --git a/assets/img/flags/AQ.png b/assets/data/img/flags/AQ.png similarity index 100% rename from assets/img/flags/AQ.png rename to assets/data/img/flags/AQ.png diff --git a/assets/img/flags/AR.png b/assets/data/img/flags/AR.png similarity index 100% rename from assets/img/flags/AR.png rename to assets/data/img/flags/AR.png diff --git a/assets/img/flags/AS.png b/assets/data/img/flags/AS.png similarity index 100% rename from assets/img/flags/AS.png rename to assets/data/img/flags/AS.png diff --git a/assets/img/flags/AT.png b/assets/data/img/flags/AT.png similarity index 100% rename from assets/img/flags/AT.png rename to assets/data/img/flags/AT.png diff --git a/assets/img/flags/AU.png b/assets/data/img/flags/AU.png similarity index 100% rename from assets/img/flags/AU.png rename to assets/data/img/flags/AU.png diff --git a/assets/img/flags/AW.png b/assets/data/img/flags/AW.png similarity index 100% rename from assets/img/flags/AW.png rename to assets/data/img/flags/AW.png diff --git a/assets/img/flags/AX.png b/assets/data/img/flags/AX.png similarity index 100% rename from assets/img/flags/AX.png rename to assets/data/img/flags/AX.png diff --git a/assets/img/flags/AZ.png b/assets/data/img/flags/AZ.png similarity index 100% rename from assets/img/flags/AZ.png rename to assets/data/img/flags/AZ.png diff --git a/assets/img/flags/BA.png b/assets/data/img/flags/BA.png similarity index 100% rename from assets/img/flags/BA.png rename to assets/data/img/flags/BA.png diff --git a/assets/img/flags/BB.png b/assets/data/img/flags/BB.png similarity index 100% rename from assets/img/flags/BB.png rename to assets/data/img/flags/BB.png diff --git a/assets/img/flags/BD.png b/assets/data/img/flags/BD.png similarity index 100% rename from assets/img/flags/BD.png rename to assets/data/img/flags/BD.png diff --git a/assets/img/flags/BE.png b/assets/data/img/flags/BE.png similarity index 100% rename from assets/img/flags/BE.png rename to assets/data/img/flags/BE.png diff --git a/assets/img/flags/BF.png b/assets/data/img/flags/BF.png similarity index 100% rename from assets/img/flags/BF.png rename to assets/data/img/flags/BF.png diff --git a/assets/img/flags/BG.png b/assets/data/img/flags/BG.png similarity index 100% rename from assets/img/flags/BG.png rename to assets/data/img/flags/BG.png diff --git a/assets/img/flags/BH.png b/assets/data/img/flags/BH.png similarity index 100% rename from assets/img/flags/BH.png rename to assets/data/img/flags/BH.png diff --git a/assets/img/flags/BI.png b/assets/data/img/flags/BI.png similarity index 100% rename from assets/img/flags/BI.png rename to assets/data/img/flags/BI.png diff --git a/assets/img/flags/BJ.png b/assets/data/img/flags/BJ.png similarity index 100% rename from assets/img/flags/BJ.png rename to assets/data/img/flags/BJ.png diff --git a/assets/img/flags/BL.png b/assets/data/img/flags/BL.png similarity index 100% rename from assets/img/flags/BL.png rename to assets/data/img/flags/BL.png diff --git a/assets/img/flags/BM.png b/assets/data/img/flags/BM.png similarity index 100% rename from assets/img/flags/BM.png rename to assets/data/img/flags/BM.png diff --git a/assets/img/flags/BN.png b/assets/data/img/flags/BN.png similarity index 100% rename from assets/img/flags/BN.png rename to assets/data/img/flags/BN.png diff --git a/assets/img/flags/BO.png b/assets/data/img/flags/BO.png similarity index 100% rename from assets/img/flags/BO.png rename to assets/data/img/flags/BO.png diff --git a/assets/img/flags/BR.png b/assets/data/img/flags/BR.png similarity index 100% rename from assets/img/flags/BR.png rename to assets/data/img/flags/BR.png diff --git a/assets/img/flags/BS.png b/assets/data/img/flags/BS.png similarity index 100% rename from assets/img/flags/BS.png rename to assets/data/img/flags/BS.png diff --git a/assets/img/flags/BT.png b/assets/data/img/flags/BT.png similarity index 100% rename from assets/img/flags/BT.png rename to assets/data/img/flags/BT.png diff --git a/assets/img/flags/BW.png b/assets/data/img/flags/BW.png similarity index 100% rename from assets/img/flags/BW.png rename to assets/data/img/flags/BW.png diff --git a/assets/img/flags/BY.png b/assets/data/img/flags/BY.png similarity index 100% rename from assets/img/flags/BY.png rename to assets/data/img/flags/BY.png diff --git a/assets/img/flags/BZ.png b/assets/data/img/flags/BZ.png similarity index 100% rename from assets/img/flags/BZ.png rename to assets/data/img/flags/BZ.png diff --git a/assets/img/flags/CA.png b/assets/data/img/flags/CA.png similarity index 100% rename from assets/img/flags/CA.png rename to assets/data/img/flags/CA.png diff --git a/assets/img/flags/CC.png b/assets/data/img/flags/CC.png similarity index 100% rename from assets/img/flags/CC.png rename to assets/data/img/flags/CC.png diff --git a/assets/img/flags/CD.png b/assets/data/img/flags/CD.png similarity index 100% rename from assets/img/flags/CD.png rename to assets/data/img/flags/CD.png diff --git a/assets/img/flags/CF.png b/assets/data/img/flags/CF.png similarity index 100% rename from assets/img/flags/CF.png rename to assets/data/img/flags/CF.png diff --git a/assets/img/flags/CG.png b/assets/data/img/flags/CG.png similarity index 100% rename from assets/img/flags/CG.png rename to assets/data/img/flags/CG.png diff --git a/assets/img/flags/CH.png b/assets/data/img/flags/CH.png similarity index 100% rename from assets/img/flags/CH.png rename to assets/data/img/flags/CH.png diff --git a/assets/img/flags/CI.png b/assets/data/img/flags/CI.png similarity index 100% rename from assets/img/flags/CI.png rename to assets/data/img/flags/CI.png diff --git a/assets/img/flags/CK.png b/assets/data/img/flags/CK.png similarity index 100% rename from assets/img/flags/CK.png rename to assets/data/img/flags/CK.png diff --git a/assets/img/flags/CL.png b/assets/data/img/flags/CL.png similarity index 100% rename from assets/img/flags/CL.png rename to assets/data/img/flags/CL.png diff --git a/assets/img/flags/CM.png b/assets/data/img/flags/CM.png similarity index 100% rename from assets/img/flags/CM.png rename to assets/data/img/flags/CM.png diff --git a/assets/img/flags/CN.png b/assets/data/img/flags/CN.png similarity index 100% rename from assets/img/flags/CN.png rename to assets/data/img/flags/CN.png diff --git a/assets/img/flags/CO.png b/assets/data/img/flags/CO.png similarity index 100% rename from assets/img/flags/CO.png rename to assets/data/img/flags/CO.png diff --git a/assets/img/flags/CR.png b/assets/data/img/flags/CR.png similarity index 100% rename from assets/img/flags/CR.png rename to assets/data/img/flags/CR.png diff --git a/assets/img/flags/CT.png b/assets/data/img/flags/CT.png similarity index 100% rename from assets/img/flags/CT.png rename to assets/data/img/flags/CT.png diff --git a/assets/img/flags/CU.png b/assets/data/img/flags/CU.png similarity index 100% rename from assets/img/flags/CU.png rename to assets/data/img/flags/CU.png diff --git a/assets/img/flags/CV.png b/assets/data/img/flags/CV.png similarity index 100% rename from assets/img/flags/CV.png rename to assets/data/img/flags/CV.png diff --git a/assets/img/flags/CW.png b/assets/data/img/flags/CW.png similarity index 100% rename from assets/img/flags/CW.png rename to assets/data/img/flags/CW.png diff --git a/assets/img/flags/CX.png b/assets/data/img/flags/CX.png similarity index 100% rename from assets/img/flags/CX.png rename to assets/data/img/flags/CX.png diff --git a/assets/img/flags/CY.png b/assets/data/img/flags/CY.png similarity index 100% rename from assets/img/flags/CY.png rename to assets/data/img/flags/CY.png diff --git a/assets/img/flags/CZ.png b/assets/data/img/flags/CZ.png similarity index 100% rename from assets/img/flags/CZ.png rename to assets/data/img/flags/CZ.png diff --git a/assets/img/flags/DE.png b/assets/data/img/flags/DE.png similarity index 100% rename from assets/img/flags/DE.png rename to assets/data/img/flags/DE.png diff --git a/assets/img/flags/DJ.png b/assets/data/img/flags/DJ.png similarity index 100% rename from assets/img/flags/DJ.png rename to assets/data/img/flags/DJ.png diff --git a/assets/img/flags/DK.png b/assets/data/img/flags/DK.png similarity index 100% rename from assets/img/flags/DK.png rename to assets/data/img/flags/DK.png diff --git a/assets/img/flags/DM.png b/assets/data/img/flags/DM.png similarity index 100% rename from assets/img/flags/DM.png rename to assets/data/img/flags/DM.png diff --git a/assets/img/flags/DO.png b/assets/data/img/flags/DO.png similarity index 100% rename from assets/img/flags/DO.png rename to assets/data/img/flags/DO.png diff --git a/assets/img/flags/DZ.png b/assets/data/img/flags/DZ.png similarity index 100% rename from assets/img/flags/DZ.png rename to assets/data/img/flags/DZ.png diff --git a/assets/img/flags/EC.png b/assets/data/img/flags/EC.png similarity index 100% rename from assets/img/flags/EC.png rename to assets/data/img/flags/EC.png diff --git a/assets/img/flags/EE.png b/assets/data/img/flags/EE.png similarity index 100% rename from assets/img/flags/EE.png rename to assets/data/img/flags/EE.png diff --git a/assets/img/flags/EG.png b/assets/data/img/flags/EG.png similarity index 100% rename from assets/img/flags/EG.png rename to assets/data/img/flags/EG.png diff --git a/assets/img/flags/EH.png b/assets/data/img/flags/EH.png similarity index 100% rename from assets/img/flags/EH.png rename to assets/data/img/flags/EH.png diff --git a/assets/img/flags/ER.png b/assets/data/img/flags/ER.png similarity index 100% rename from assets/img/flags/ER.png rename to assets/data/img/flags/ER.png diff --git a/assets/img/flags/ES.png b/assets/data/img/flags/ES.png similarity index 100% rename from assets/img/flags/ES.png rename to assets/data/img/flags/ES.png diff --git a/assets/img/flags/ET.png b/assets/data/img/flags/ET.png similarity index 100% rename from assets/img/flags/ET.png rename to assets/data/img/flags/ET.png diff --git a/assets/img/flags/EU.png b/assets/data/img/flags/EU.png similarity index 100% rename from assets/img/flags/EU.png rename to assets/data/img/flags/EU.png diff --git a/assets/img/flags/FI.png b/assets/data/img/flags/FI.png similarity index 100% rename from assets/img/flags/FI.png rename to assets/data/img/flags/FI.png diff --git a/assets/img/flags/FJ.png b/assets/data/img/flags/FJ.png similarity index 100% rename from assets/img/flags/FJ.png rename to assets/data/img/flags/FJ.png diff --git a/assets/img/flags/FK.png b/assets/data/img/flags/FK.png similarity index 100% rename from assets/img/flags/FK.png rename to assets/data/img/flags/FK.png diff --git a/assets/img/flags/FM.png b/assets/data/img/flags/FM.png similarity index 100% rename from assets/img/flags/FM.png rename to assets/data/img/flags/FM.png diff --git a/assets/img/flags/FO.png b/assets/data/img/flags/FO.png similarity index 100% rename from assets/img/flags/FO.png rename to assets/data/img/flags/FO.png diff --git a/assets/img/flags/FR.png b/assets/data/img/flags/FR.png similarity index 100% rename from assets/img/flags/FR.png rename to assets/data/img/flags/FR.png diff --git a/assets/img/flags/GA.png b/assets/data/img/flags/GA.png similarity index 100% rename from assets/img/flags/GA.png rename to assets/data/img/flags/GA.png diff --git a/assets/img/flags/GB.png b/assets/data/img/flags/GB.png similarity index 100% rename from assets/img/flags/GB.png rename to assets/data/img/flags/GB.png diff --git a/assets/img/flags/GD.png b/assets/data/img/flags/GD.png similarity index 100% rename from assets/img/flags/GD.png rename to assets/data/img/flags/GD.png diff --git a/assets/img/flags/GE.png b/assets/data/img/flags/GE.png similarity index 100% rename from assets/img/flags/GE.png rename to assets/data/img/flags/GE.png diff --git a/assets/img/flags/GG.png b/assets/data/img/flags/GG.png similarity index 100% rename from assets/img/flags/GG.png rename to assets/data/img/flags/GG.png diff --git a/assets/img/flags/GH.png b/assets/data/img/flags/GH.png similarity index 100% rename from assets/img/flags/GH.png rename to assets/data/img/flags/GH.png diff --git a/assets/img/flags/GI.png b/assets/data/img/flags/GI.png similarity index 100% rename from assets/img/flags/GI.png rename to assets/data/img/flags/GI.png diff --git a/assets/img/flags/GL.png b/assets/data/img/flags/GL.png similarity index 100% rename from assets/img/flags/GL.png rename to assets/data/img/flags/GL.png diff --git a/assets/img/flags/GM.png b/assets/data/img/flags/GM.png similarity index 100% rename from assets/img/flags/GM.png rename to assets/data/img/flags/GM.png diff --git a/assets/img/flags/GN.png b/assets/data/img/flags/GN.png similarity index 100% rename from assets/img/flags/GN.png rename to assets/data/img/flags/GN.png diff --git a/assets/img/flags/GQ.png b/assets/data/img/flags/GQ.png similarity index 100% rename from assets/img/flags/GQ.png rename to assets/data/img/flags/GQ.png diff --git a/assets/img/flags/GR.png b/assets/data/img/flags/GR.png similarity index 100% rename from assets/img/flags/GR.png rename to assets/data/img/flags/GR.png diff --git a/assets/img/flags/GS.png b/assets/data/img/flags/GS.png similarity index 100% rename from assets/img/flags/GS.png rename to assets/data/img/flags/GS.png diff --git a/assets/img/flags/GT.png b/assets/data/img/flags/GT.png similarity index 100% rename from assets/img/flags/GT.png rename to assets/data/img/flags/GT.png diff --git a/assets/img/flags/GU.png b/assets/data/img/flags/GU.png similarity index 100% rename from assets/img/flags/GU.png rename to assets/data/img/flags/GU.png diff --git a/assets/img/flags/GW.png b/assets/data/img/flags/GW.png similarity index 100% rename from assets/img/flags/GW.png rename to assets/data/img/flags/GW.png diff --git a/assets/img/flags/GY.png b/assets/data/img/flags/GY.png similarity index 100% rename from assets/img/flags/GY.png rename to assets/data/img/flags/GY.png diff --git a/assets/img/flags/HK.png b/assets/data/img/flags/HK.png similarity index 100% rename from assets/img/flags/HK.png rename to assets/data/img/flags/HK.png diff --git a/assets/img/flags/HN.png b/assets/data/img/flags/HN.png similarity index 100% rename from assets/img/flags/HN.png rename to assets/data/img/flags/HN.png diff --git a/assets/img/flags/HR.png b/assets/data/img/flags/HR.png similarity index 100% rename from assets/img/flags/HR.png rename to assets/data/img/flags/HR.png diff --git a/assets/img/flags/HT.png b/assets/data/img/flags/HT.png similarity index 100% rename from assets/img/flags/HT.png rename to assets/data/img/flags/HT.png diff --git a/assets/img/flags/HU.png b/assets/data/img/flags/HU.png similarity index 100% rename from assets/img/flags/HU.png rename to assets/data/img/flags/HU.png diff --git a/assets/img/flags/IC.png b/assets/data/img/flags/IC.png similarity index 100% rename from assets/img/flags/IC.png rename to assets/data/img/flags/IC.png diff --git a/assets/img/flags/ID.png b/assets/data/img/flags/ID.png similarity index 100% rename from assets/img/flags/ID.png rename to assets/data/img/flags/ID.png diff --git a/assets/img/flags/IE.png b/assets/data/img/flags/IE.png similarity index 100% rename from assets/img/flags/IE.png rename to assets/data/img/flags/IE.png diff --git a/assets/img/flags/IL.png b/assets/data/img/flags/IL.png similarity index 100% rename from assets/img/flags/IL.png rename to assets/data/img/flags/IL.png diff --git a/assets/img/flags/IM.png b/assets/data/img/flags/IM.png similarity index 100% rename from assets/img/flags/IM.png rename to assets/data/img/flags/IM.png diff --git a/assets/img/flags/IN.png b/assets/data/img/flags/IN.png similarity index 100% rename from assets/img/flags/IN.png rename to assets/data/img/flags/IN.png diff --git a/assets/img/flags/IQ.png b/assets/data/img/flags/IQ.png similarity index 100% rename from assets/img/flags/IQ.png rename to assets/data/img/flags/IQ.png diff --git a/assets/img/flags/IR.png b/assets/data/img/flags/IR.png similarity index 100% rename from assets/img/flags/IR.png rename to assets/data/img/flags/IR.png diff --git a/assets/img/flags/IS.png b/assets/data/img/flags/IS.png similarity index 100% rename from assets/img/flags/IS.png rename to assets/data/img/flags/IS.png diff --git a/assets/img/flags/IT.png b/assets/data/img/flags/IT.png similarity index 100% rename from assets/img/flags/IT.png rename to assets/data/img/flags/IT.png diff --git a/assets/img/flags/JE.png b/assets/data/img/flags/JE.png similarity index 100% rename from assets/img/flags/JE.png rename to assets/data/img/flags/JE.png diff --git a/assets/img/flags/JM.png b/assets/data/img/flags/JM.png similarity index 100% rename from assets/img/flags/JM.png rename to assets/data/img/flags/JM.png diff --git a/assets/img/flags/JO.png b/assets/data/img/flags/JO.png similarity index 100% rename from assets/img/flags/JO.png rename to assets/data/img/flags/JO.png diff --git a/assets/img/flags/JP.png b/assets/data/img/flags/JP.png similarity index 100% rename from assets/img/flags/JP.png rename to assets/data/img/flags/JP.png diff --git a/assets/img/flags/KE.png b/assets/data/img/flags/KE.png similarity index 100% rename from assets/img/flags/KE.png rename to assets/data/img/flags/KE.png diff --git a/assets/img/flags/KG.png b/assets/data/img/flags/KG.png similarity index 100% rename from assets/img/flags/KG.png rename to assets/data/img/flags/KG.png diff --git a/assets/img/flags/KH.png b/assets/data/img/flags/KH.png similarity index 100% rename from assets/img/flags/KH.png rename to assets/data/img/flags/KH.png diff --git a/assets/img/flags/KI.png b/assets/data/img/flags/KI.png similarity index 100% rename from assets/img/flags/KI.png rename to assets/data/img/flags/KI.png diff --git a/assets/img/flags/KM.png b/assets/data/img/flags/KM.png similarity index 100% rename from assets/img/flags/KM.png rename to assets/data/img/flags/KM.png diff --git a/assets/img/flags/KN.png b/assets/data/img/flags/KN.png similarity index 100% rename from assets/img/flags/KN.png rename to assets/data/img/flags/KN.png diff --git a/assets/img/flags/KP.png b/assets/data/img/flags/KP.png similarity index 100% rename from assets/img/flags/KP.png rename to assets/data/img/flags/KP.png diff --git a/assets/img/flags/KR.png b/assets/data/img/flags/KR.png similarity index 100% rename from assets/img/flags/KR.png rename to assets/data/img/flags/KR.png diff --git a/assets/img/flags/KW.png b/assets/data/img/flags/KW.png similarity index 100% rename from assets/img/flags/KW.png rename to assets/data/img/flags/KW.png diff --git a/assets/img/flags/KY.png b/assets/data/img/flags/KY.png similarity index 100% rename from assets/img/flags/KY.png rename to assets/data/img/flags/KY.png diff --git a/assets/img/flags/KZ.png b/assets/data/img/flags/KZ.png similarity index 100% rename from assets/img/flags/KZ.png rename to assets/data/img/flags/KZ.png diff --git a/assets/img/flags/LA.png b/assets/data/img/flags/LA.png similarity index 100% rename from assets/img/flags/LA.png rename to assets/data/img/flags/LA.png diff --git a/assets/img/flags/LB.png b/assets/data/img/flags/LB.png similarity index 100% rename from assets/img/flags/LB.png rename to assets/data/img/flags/LB.png diff --git a/assets/img/flags/LC.png b/assets/data/img/flags/LC.png similarity index 100% rename from assets/img/flags/LC.png rename to assets/data/img/flags/LC.png diff --git a/assets/img/flags/LI.png b/assets/data/img/flags/LI.png similarity index 100% rename from assets/img/flags/LI.png rename to assets/data/img/flags/LI.png diff --git a/assets/img/flags/LICENSE.txt b/assets/data/img/flags/LICENSE.txt similarity index 100% rename from assets/img/flags/LICENSE.txt rename to assets/data/img/flags/LICENSE.txt diff --git a/assets/img/flags/LK.png b/assets/data/img/flags/LK.png similarity index 100% rename from assets/img/flags/LK.png rename to assets/data/img/flags/LK.png diff --git a/assets/img/flags/LR.png b/assets/data/img/flags/LR.png similarity index 100% rename from assets/img/flags/LR.png rename to assets/data/img/flags/LR.png diff --git a/assets/img/flags/LS.png b/assets/data/img/flags/LS.png similarity index 100% rename from assets/img/flags/LS.png rename to assets/data/img/flags/LS.png diff --git a/assets/img/flags/LT.png b/assets/data/img/flags/LT.png similarity index 100% rename from assets/img/flags/LT.png rename to assets/data/img/flags/LT.png diff --git a/assets/img/flags/LU.png b/assets/data/img/flags/LU.png similarity index 100% rename from assets/img/flags/LU.png rename to assets/data/img/flags/LU.png diff --git a/assets/img/flags/LV.png b/assets/data/img/flags/LV.png similarity index 100% rename from assets/img/flags/LV.png rename to assets/data/img/flags/LV.png diff --git a/assets/img/flags/LY.png b/assets/data/img/flags/LY.png similarity index 100% rename from assets/img/flags/LY.png rename to assets/data/img/flags/LY.png diff --git a/assets/img/flags/MA.png b/assets/data/img/flags/MA.png similarity index 100% rename from assets/img/flags/MA.png rename to assets/data/img/flags/MA.png diff --git a/assets/img/flags/MC.png b/assets/data/img/flags/MC.png similarity index 100% rename from assets/img/flags/MC.png rename to assets/data/img/flags/MC.png diff --git a/assets/img/flags/MD.png b/assets/data/img/flags/MD.png similarity index 100% rename from assets/img/flags/MD.png rename to assets/data/img/flags/MD.png diff --git a/assets/img/flags/ME.png b/assets/data/img/flags/ME.png similarity index 100% rename from assets/img/flags/ME.png rename to assets/data/img/flags/ME.png diff --git a/assets/img/flags/MF.png b/assets/data/img/flags/MF.png similarity index 100% rename from assets/img/flags/MF.png rename to assets/data/img/flags/MF.png diff --git a/assets/img/flags/MG.png b/assets/data/img/flags/MG.png similarity index 100% rename from assets/img/flags/MG.png rename to assets/data/img/flags/MG.png diff --git a/assets/img/flags/MH.png b/assets/data/img/flags/MH.png similarity index 100% rename from assets/img/flags/MH.png rename to assets/data/img/flags/MH.png diff --git a/assets/img/flags/MK.png b/assets/data/img/flags/MK.png similarity index 100% rename from assets/img/flags/MK.png rename to assets/data/img/flags/MK.png diff --git a/assets/img/flags/ML.png b/assets/data/img/flags/ML.png similarity index 100% rename from assets/img/flags/ML.png rename to assets/data/img/flags/ML.png diff --git a/assets/img/flags/MM.png b/assets/data/img/flags/MM.png similarity index 100% rename from assets/img/flags/MM.png rename to assets/data/img/flags/MM.png diff --git a/assets/img/flags/MN.png b/assets/data/img/flags/MN.png similarity index 100% rename from assets/img/flags/MN.png rename to assets/data/img/flags/MN.png diff --git a/assets/img/flags/MO.png b/assets/data/img/flags/MO.png similarity index 100% rename from assets/img/flags/MO.png rename to assets/data/img/flags/MO.png diff --git a/assets/img/flags/MP.png b/assets/data/img/flags/MP.png similarity index 100% rename from assets/img/flags/MP.png rename to assets/data/img/flags/MP.png diff --git a/assets/img/flags/MQ.png b/assets/data/img/flags/MQ.png similarity index 100% rename from assets/img/flags/MQ.png rename to assets/data/img/flags/MQ.png diff --git a/assets/img/flags/MR.png b/assets/data/img/flags/MR.png similarity index 100% rename from assets/img/flags/MR.png rename to assets/data/img/flags/MR.png diff --git a/assets/img/flags/MS.png b/assets/data/img/flags/MS.png similarity index 100% rename from assets/img/flags/MS.png rename to assets/data/img/flags/MS.png diff --git a/assets/img/flags/MT.png b/assets/data/img/flags/MT.png similarity index 100% rename from assets/img/flags/MT.png rename to assets/data/img/flags/MT.png diff --git a/assets/img/flags/MU.png b/assets/data/img/flags/MU.png similarity index 100% rename from assets/img/flags/MU.png rename to assets/data/img/flags/MU.png diff --git a/assets/img/flags/MV.png b/assets/data/img/flags/MV.png similarity index 100% rename from assets/img/flags/MV.png rename to assets/data/img/flags/MV.png diff --git a/assets/img/flags/MW.png b/assets/data/img/flags/MW.png similarity index 100% rename from assets/img/flags/MW.png rename to assets/data/img/flags/MW.png diff --git a/assets/img/flags/MX.png b/assets/data/img/flags/MX.png similarity index 100% rename from assets/img/flags/MX.png rename to assets/data/img/flags/MX.png diff --git a/assets/img/flags/MY.png b/assets/data/img/flags/MY.png similarity index 100% rename from assets/img/flags/MY.png rename to assets/data/img/flags/MY.png diff --git a/assets/img/flags/MZ.png b/assets/data/img/flags/MZ.png similarity index 100% rename from assets/img/flags/MZ.png rename to assets/data/img/flags/MZ.png diff --git a/assets/img/flags/NA.png b/assets/data/img/flags/NA.png similarity index 100% rename from assets/img/flags/NA.png rename to assets/data/img/flags/NA.png diff --git a/assets/img/flags/NC.png b/assets/data/img/flags/NC.png similarity index 100% rename from assets/img/flags/NC.png rename to assets/data/img/flags/NC.png diff --git a/assets/img/flags/NE.png b/assets/data/img/flags/NE.png similarity index 100% rename from assets/img/flags/NE.png rename to assets/data/img/flags/NE.png diff --git a/assets/img/flags/NF.png b/assets/data/img/flags/NF.png similarity index 100% rename from assets/img/flags/NF.png rename to assets/data/img/flags/NF.png diff --git a/assets/img/flags/NG.png b/assets/data/img/flags/NG.png similarity index 100% rename from assets/img/flags/NG.png rename to assets/data/img/flags/NG.png diff --git a/assets/img/flags/NI.png b/assets/data/img/flags/NI.png similarity index 100% rename from assets/img/flags/NI.png rename to assets/data/img/flags/NI.png diff --git a/assets/img/flags/NL.png b/assets/data/img/flags/NL.png similarity index 100% rename from assets/img/flags/NL.png rename to assets/data/img/flags/NL.png diff --git a/assets/img/flags/NO.png b/assets/data/img/flags/NO.png similarity index 100% rename from assets/img/flags/NO.png rename to assets/data/img/flags/NO.png diff --git a/assets/img/flags/NP.png b/assets/data/img/flags/NP.png similarity index 100% rename from assets/img/flags/NP.png rename to assets/data/img/flags/NP.png diff --git a/assets/img/flags/NR.png b/assets/data/img/flags/NR.png similarity index 100% rename from assets/img/flags/NR.png rename to assets/data/img/flags/NR.png diff --git a/assets/img/flags/NU.png b/assets/data/img/flags/NU.png similarity index 100% rename from assets/img/flags/NU.png rename to assets/data/img/flags/NU.png diff --git a/assets/img/flags/NZ.png b/assets/data/img/flags/NZ.png similarity index 100% rename from assets/img/flags/NZ.png rename to assets/data/img/flags/NZ.png diff --git a/assets/img/flags/OM.png b/assets/data/img/flags/OM.png similarity index 100% rename from assets/img/flags/OM.png rename to assets/data/img/flags/OM.png diff --git a/assets/img/flags/PA.png b/assets/data/img/flags/PA.png similarity index 100% rename from assets/img/flags/PA.png rename to assets/data/img/flags/PA.png diff --git a/assets/img/flags/PE.png b/assets/data/img/flags/PE.png similarity index 100% rename from assets/img/flags/PE.png rename to assets/data/img/flags/PE.png diff --git a/assets/img/flags/PF.png b/assets/data/img/flags/PF.png similarity index 100% rename from assets/img/flags/PF.png rename to assets/data/img/flags/PF.png diff --git a/assets/img/flags/PG.png b/assets/data/img/flags/PG.png similarity index 100% rename from assets/img/flags/PG.png rename to assets/data/img/flags/PG.png diff --git a/assets/img/flags/PH.png b/assets/data/img/flags/PH.png similarity index 100% rename from assets/img/flags/PH.png rename to assets/data/img/flags/PH.png diff --git a/assets/img/flags/PK.png b/assets/data/img/flags/PK.png similarity index 100% rename from assets/img/flags/PK.png rename to assets/data/img/flags/PK.png diff --git a/assets/img/flags/PL.png b/assets/data/img/flags/PL.png similarity index 100% rename from assets/img/flags/PL.png rename to assets/data/img/flags/PL.png diff --git a/assets/img/flags/PN.png b/assets/data/img/flags/PN.png similarity index 100% rename from assets/img/flags/PN.png rename to assets/data/img/flags/PN.png diff --git a/assets/img/flags/PR.png b/assets/data/img/flags/PR.png similarity index 100% rename from assets/img/flags/PR.png rename to assets/data/img/flags/PR.png diff --git a/assets/img/flags/PS.png b/assets/data/img/flags/PS.png similarity index 100% rename from assets/img/flags/PS.png rename to assets/data/img/flags/PS.png diff --git a/assets/img/flags/PT.png b/assets/data/img/flags/PT.png similarity index 100% rename from assets/img/flags/PT.png rename to assets/data/img/flags/PT.png diff --git a/assets/img/flags/PW.png b/assets/data/img/flags/PW.png similarity index 100% rename from assets/img/flags/PW.png rename to assets/data/img/flags/PW.png diff --git a/assets/img/flags/PY.png b/assets/data/img/flags/PY.png similarity index 100% rename from assets/img/flags/PY.png rename to assets/data/img/flags/PY.png diff --git a/assets/img/flags/QA.png b/assets/data/img/flags/QA.png similarity index 100% rename from assets/img/flags/QA.png rename to assets/data/img/flags/QA.png diff --git a/assets/img/flags/RE.png b/assets/data/img/flags/RE.png similarity index 100% rename from assets/img/flags/RE.png rename to assets/data/img/flags/RE.png diff --git a/assets/img/flags/RO.png b/assets/data/img/flags/RO.png similarity index 100% rename from assets/img/flags/RO.png rename to assets/data/img/flags/RO.png diff --git a/assets/img/flags/RS.png b/assets/data/img/flags/RS.png similarity index 100% rename from assets/img/flags/RS.png rename to assets/data/img/flags/RS.png diff --git a/assets/img/flags/RU.png b/assets/data/img/flags/RU.png similarity index 100% rename from assets/img/flags/RU.png rename to assets/data/img/flags/RU.png diff --git a/assets/img/flags/RW.png b/assets/data/img/flags/RW.png similarity index 100% rename from assets/img/flags/RW.png rename to assets/data/img/flags/RW.png diff --git a/assets/img/flags/SA.png b/assets/data/img/flags/SA.png similarity index 100% rename from assets/img/flags/SA.png rename to assets/data/img/flags/SA.png diff --git a/assets/img/flags/SB.png b/assets/data/img/flags/SB.png similarity index 100% rename from assets/img/flags/SB.png rename to assets/data/img/flags/SB.png diff --git a/assets/img/flags/SC.png b/assets/data/img/flags/SC.png similarity index 100% rename from assets/img/flags/SC.png rename to assets/data/img/flags/SC.png diff --git a/assets/img/flags/SD.png b/assets/data/img/flags/SD.png similarity index 100% rename from assets/img/flags/SD.png rename to assets/data/img/flags/SD.png diff --git a/assets/img/flags/SE.png b/assets/data/img/flags/SE.png similarity index 100% rename from assets/img/flags/SE.png rename to assets/data/img/flags/SE.png diff --git a/assets/img/flags/SG.png b/assets/data/img/flags/SG.png similarity index 100% rename from assets/img/flags/SG.png rename to assets/data/img/flags/SG.png diff --git a/assets/img/flags/SH.png b/assets/data/img/flags/SH.png similarity index 100% rename from assets/img/flags/SH.png rename to assets/data/img/flags/SH.png diff --git a/assets/img/flags/SI.png b/assets/data/img/flags/SI.png similarity index 100% rename from assets/img/flags/SI.png rename to assets/data/img/flags/SI.png diff --git a/assets/img/flags/SK.png b/assets/data/img/flags/SK.png similarity index 100% rename from assets/img/flags/SK.png rename to assets/data/img/flags/SK.png diff --git a/assets/img/flags/SL.png b/assets/data/img/flags/SL.png similarity index 100% rename from assets/img/flags/SL.png rename to assets/data/img/flags/SL.png diff --git a/assets/img/flags/SM.png b/assets/data/img/flags/SM.png similarity index 100% rename from assets/img/flags/SM.png rename to assets/data/img/flags/SM.png diff --git a/assets/img/flags/SN.png b/assets/data/img/flags/SN.png similarity index 100% rename from assets/img/flags/SN.png rename to assets/data/img/flags/SN.png diff --git a/assets/img/flags/SO.png b/assets/data/img/flags/SO.png similarity index 100% rename from assets/img/flags/SO.png rename to assets/data/img/flags/SO.png diff --git a/assets/img/flags/SR.png b/assets/data/img/flags/SR.png similarity index 100% rename from assets/img/flags/SR.png rename to assets/data/img/flags/SR.png diff --git a/assets/img/flags/SS.png b/assets/data/img/flags/SS.png similarity index 100% rename from assets/img/flags/SS.png rename to assets/data/img/flags/SS.png diff --git a/assets/img/flags/ST.png b/assets/data/img/flags/ST.png similarity index 100% rename from assets/img/flags/ST.png rename to assets/data/img/flags/ST.png diff --git a/assets/img/flags/SV.png b/assets/data/img/flags/SV.png similarity index 100% rename from assets/img/flags/SV.png rename to assets/data/img/flags/SV.png diff --git a/assets/img/flags/SX.png b/assets/data/img/flags/SX.png similarity index 100% rename from assets/img/flags/SX.png rename to assets/data/img/flags/SX.png diff --git a/assets/img/flags/SY.png b/assets/data/img/flags/SY.png similarity index 100% rename from assets/img/flags/SY.png rename to assets/data/img/flags/SY.png diff --git a/assets/img/flags/SZ.png b/assets/data/img/flags/SZ.png similarity index 100% rename from assets/img/flags/SZ.png rename to assets/data/img/flags/SZ.png diff --git a/assets/img/flags/TC.png b/assets/data/img/flags/TC.png similarity index 100% rename from assets/img/flags/TC.png rename to assets/data/img/flags/TC.png diff --git a/assets/img/flags/TD.png b/assets/data/img/flags/TD.png similarity index 100% rename from assets/img/flags/TD.png rename to assets/data/img/flags/TD.png diff --git a/assets/img/flags/TF.png b/assets/data/img/flags/TF.png similarity index 100% rename from assets/img/flags/TF.png rename to assets/data/img/flags/TF.png diff --git a/assets/img/flags/TG.png b/assets/data/img/flags/TG.png similarity index 100% rename from assets/img/flags/TG.png rename to assets/data/img/flags/TG.png diff --git a/assets/img/flags/TH.png b/assets/data/img/flags/TH.png similarity index 100% rename from assets/img/flags/TH.png rename to assets/data/img/flags/TH.png diff --git a/assets/img/flags/TJ.png b/assets/data/img/flags/TJ.png similarity index 100% rename from assets/img/flags/TJ.png rename to assets/data/img/flags/TJ.png diff --git a/assets/img/flags/TK.png b/assets/data/img/flags/TK.png similarity index 100% rename from assets/img/flags/TK.png rename to assets/data/img/flags/TK.png diff --git a/assets/img/flags/TL.png b/assets/data/img/flags/TL.png similarity index 100% rename from assets/img/flags/TL.png rename to assets/data/img/flags/TL.png diff --git a/assets/img/flags/TM.png b/assets/data/img/flags/TM.png similarity index 100% rename from assets/img/flags/TM.png rename to assets/data/img/flags/TM.png diff --git a/assets/img/flags/TN.png b/assets/data/img/flags/TN.png similarity index 100% rename from assets/img/flags/TN.png rename to assets/data/img/flags/TN.png diff --git a/assets/img/flags/TO.png b/assets/data/img/flags/TO.png similarity index 100% rename from assets/img/flags/TO.png rename to assets/data/img/flags/TO.png diff --git a/assets/img/flags/TR.png b/assets/data/img/flags/TR.png similarity index 100% rename from assets/img/flags/TR.png rename to assets/data/img/flags/TR.png diff --git a/assets/img/flags/TT.png b/assets/data/img/flags/TT.png similarity index 100% rename from assets/img/flags/TT.png rename to assets/data/img/flags/TT.png diff --git a/assets/img/flags/TV.png b/assets/data/img/flags/TV.png similarity index 100% rename from assets/img/flags/TV.png rename to assets/data/img/flags/TV.png diff --git a/assets/img/flags/TW.png b/assets/data/img/flags/TW.png similarity index 100% rename from assets/img/flags/TW.png rename to assets/data/img/flags/TW.png diff --git a/assets/img/flags/TZ.png b/assets/data/img/flags/TZ.png similarity index 100% rename from assets/img/flags/TZ.png rename to assets/data/img/flags/TZ.png diff --git a/assets/img/flags/UA.png b/assets/data/img/flags/UA.png similarity index 100% rename from assets/img/flags/UA.png rename to assets/data/img/flags/UA.png diff --git a/assets/img/flags/UG.png b/assets/data/img/flags/UG.png similarity index 100% rename from assets/img/flags/UG.png rename to assets/data/img/flags/UG.png diff --git a/assets/img/flags/US.png b/assets/data/img/flags/US.png similarity index 100% rename from assets/img/flags/US.png rename to assets/data/img/flags/US.png diff --git a/assets/img/flags/UY.png b/assets/data/img/flags/UY.png similarity index 100% rename from assets/img/flags/UY.png rename to assets/data/img/flags/UY.png diff --git a/assets/img/flags/UZ.png b/assets/data/img/flags/UZ.png similarity index 100% rename from assets/img/flags/UZ.png rename to assets/data/img/flags/UZ.png diff --git a/assets/img/flags/VA.png b/assets/data/img/flags/VA.png similarity index 100% rename from assets/img/flags/VA.png rename to assets/data/img/flags/VA.png diff --git a/assets/img/flags/VC.png b/assets/data/img/flags/VC.png similarity index 100% rename from assets/img/flags/VC.png rename to assets/data/img/flags/VC.png diff --git a/assets/img/flags/VE.png b/assets/data/img/flags/VE.png similarity index 100% rename from assets/img/flags/VE.png rename to assets/data/img/flags/VE.png diff --git a/assets/img/flags/VG.png b/assets/data/img/flags/VG.png similarity index 100% rename from assets/img/flags/VG.png rename to assets/data/img/flags/VG.png diff --git a/assets/img/flags/VI.png b/assets/data/img/flags/VI.png similarity index 100% rename from assets/img/flags/VI.png rename to assets/data/img/flags/VI.png diff --git a/assets/img/flags/VN.png b/assets/data/img/flags/VN.png similarity index 100% rename from assets/img/flags/VN.png rename to assets/data/img/flags/VN.png diff --git a/assets/img/flags/VU.png b/assets/data/img/flags/VU.png similarity index 100% rename from assets/img/flags/VU.png rename to assets/data/img/flags/VU.png diff --git a/assets/img/flags/WF.png b/assets/data/img/flags/WF.png similarity index 100% rename from assets/img/flags/WF.png rename to assets/data/img/flags/WF.png diff --git a/assets/img/flags/WS.png b/assets/data/img/flags/WS.png similarity index 100% rename from assets/img/flags/WS.png rename to assets/data/img/flags/WS.png diff --git a/assets/img/flags/YE.png b/assets/data/img/flags/YE.png similarity index 100% rename from assets/img/flags/YE.png rename to assets/data/img/flags/YE.png diff --git a/assets/img/flags/YT.png b/assets/data/img/flags/YT.png similarity index 100% rename from assets/img/flags/YT.png rename to assets/data/img/flags/YT.png diff --git a/assets/img/flags/ZA.png b/assets/data/img/flags/ZA.png similarity index 100% rename from assets/img/flags/ZA.png rename to assets/data/img/flags/ZA.png diff --git a/assets/img/flags/ZM.png b/assets/data/img/flags/ZM.png similarity index 100% rename from assets/img/flags/ZM.png rename to assets/data/img/flags/ZM.png diff --git a/assets/img/flags/ZW.png b/assets/data/img/flags/ZW.png similarity index 100% rename from assets/img/flags/ZW.png rename to assets/data/img/flags/ZW.png diff --git a/assets/img/flags/_abkhazia.png b/assets/data/img/flags/_abkhazia.png similarity index 100% rename from assets/img/flags/_abkhazia.png rename to assets/data/img/flags/_abkhazia.png diff --git a/assets/img/flags/_basque-country.png b/assets/data/img/flags/_basque-country.png similarity index 100% rename from assets/img/flags/_basque-country.png rename to assets/data/img/flags/_basque-country.png diff --git a/assets/img/flags/_british-antarctic-territory.png b/assets/data/img/flags/_british-antarctic-territory.png similarity index 100% rename from assets/img/flags/_british-antarctic-territory.png rename to assets/data/img/flags/_british-antarctic-territory.png diff --git a/assets/img/flags/_commonwealth.png b/assets/data/img/flags/_commonwealth.png similarity index 100% rename from assets/img/flags/_commonwealth.png rename to assets/data/img/flags/_commonwealth.png diff --git a/assets/img/flags/_england.png b/assets/data/img/flags/_england.png similarity index 100% rename from assets/img/flags/_england.png rename to assets/data/img/flags/_england.png diff --git a/assets/img/flags/_gosquared.png b/assets/data/img/flags/_gosquared.png similarity index 100% rename from assets/img/flags/_gosquared.png rename to assets/data/img/flags/_gosquared.png diff --git a/assets/img/flags/_kosovo.png b/assets/data/img/flags/_kosovo.png similarity index 100% rename from assets/img/flags/_kosovo.png rename to assets/data/img/flags/_kosovo.png diff --git a/assets/img/flags/_mars.png b/assets/data/img/flags/_mars.png similarity index 100% rename from assets/img/flags/_mars.png rename to assets/data/img/flags/_mars.png diff --git a/assets/img/flags/_nagorno-karabakh.png b/assets/data/img/flags/_nagorno-karabakh.png similarity index 100% rename from assets/img/flags/_nagorno-karabakh.png rename to assets/data/img/flags/_nagorno-karabakh.png diff --git a/assets/img/flags/_nato.png b/assets/data/img/flags/_nato.png similarity index 100% rename from assets/img/flags/_nato.png rename to assets/data/img/flags/_nato.png diff --git a/assets/img/flags/_northern-cyprus.png b/assets/data/img/flags/_northern-cyprus.png similarity index 100% rename from assets/img/flags/_northern-cyprus.png rename to assets/data/img/flags/_northern-cyprus.png diff --git a/assets/img/flags/_olympics.png b/assets/data/img/flags/_olympics.png similarity index 100% rename from assets/img/flags/_olympics.png rename to assets/data/img/flags/_olympics.png diff --git a/assets/img/flags/_red-cross.png b/assets/data/img/flags/_red-cross.png similarity index 100% rename from assets/img/flags/_red-cross.png rename to assets/data/img/flags/_red-cross.png diff --git a/assets/img/flags/_scotland.png b/assets/data/img/flags/_scotland.png similarity index 100% rename from assets/img/flags/_scotland.png rename to assets/data/img/flags/_scotland.png diff --git a/assets/img/flags/_somaliland.png b/assets/data/img/flags/_somaliland.png similarity index 100% rename from assets/img/flags/_somaliland.png rename to assets/data/img/flags/_somaliland.png diff --git a/assets/img/flags/_south-ossetia.png b/assets/data/img/flags/_south-ossetia.png similarity index 100% rename from assets/img/flags/_south-ossetia.png rename to assets/data/img/flags/_south-ossetia.png diff --git a/assets/img/flags/_united-nations.png b/assets/data/img/flags/_united-nations.png similarity index 100% rename from assets/img/flags/_united-nations.png rename to assets/data/img/flags/_united-nations.png diff --git a/assets/img/flags/_unknown.png b/assets/data/img/flags/_unknown.png similarity index 100% rename from assets/img/flags/_unknown.png rename to assets/data/img/flags/_unknown.png diff --git a/assets/img/flags/_wales.png b/assets/data/img/flags/_wales.png similarity index 100% rename from assets/img/flags/_wales.png rename to assets/data/img/flags/_wales.png diff --git a/assets/img/linux.svg b/assets/data/img/linux.svg similarity index 100% rename from assets/img/linux.svg rename to assets/data/img/linux.svg diff --git a/assets/img/mac.svg b/assets/data/img/mac.svg similarity index 100% rename from assets/img/mac.svg rename to assets/data/img/mac.svg diff --git a/assets/img/plants1-br.png b/assets/data/img/plants1-br.png similarity index 100% rename from assets/img/plants1-br.png rename to assets/data/img/plants1-br.png diff --git a/assets/img/plants1.png b/assets/data/img/plants1.png similarity index 100% rename from assets/img/plants1.png rename to assets/data/img/plants1.png diff --git a/assets/img/spn-feature-carousel/access-regional-content-easily.png b/assets/data/img/spn-feature-carousel/access-regional-content-easily.png similarity index 100% rename from assets/img/spn-feature-carousel/access-regional-content-easily.png rename to assets/data/img/spn-feature-carousel/access-regional-content-easily.png diff --git a/assets/img/spn-feature-carousel/built-from-the-ground-up.png b/assets/data/img/spn-feature-carousel/built-from-the-ground-up.png similarity index 100% rename from assets/img/spn-feature-carousel/built-from-the-ground-up.png rename to assets/data/img/spn-feature-carousel/built-from-the-ground-up.png diff --git a/assets/img/spn-feature-carousel/bye-bye-vpns.png b/assets/data/img/spn-feature-carousel/bye-bye-vpns.png similarity index 100% rename from assets/img/spn-feature-carousel/bye-bye-vpns.png rename to assets/data/img/spn-feature-carousel/bye-bye-vpns.png diff --git a/assets/img/spn-feature-carousel/easily-control-your-privacy.png b/assets/data/img/spn-feature-carousel/easily-control-your-privacy.png similarity index 100% rename from assets/img/spn-feature-carousel/easily-control-your-privacy.png rename to assets/data/img/spn-feature-carousel/easily-control-your-privacy.png diff --git a/assets/img/spn-feature-carousel/multiple-identities-for-each-app.png b/assets/data/img/spn-feature-carousel/multiple-identities-for-each-app.png similarity index 100% rename from assets/img/spn-feature-carousel/multiple-identities-for-each-app.png rename to assets/data/img/spn-feature-carousel/multiple-identities-for-each-app.png diff --git a/assets/img/spn-login.png b/assets/data/img/spn-login.png similarity index 100% rename from assets/img/spn-login.png rename to assets/data/img/spn-login.png diff --git a/assets/img/windows.svg b/assets/data/img/windows.svg similarity index 100% rename from assets/img/windows.svg rename to assets/data/img/windows.svg diff --git a/assets/world-50m.json b/assets/data/world-50m.json similarity index 100% rename from assets/world-50m.json rename to assets/data/world-50m.json diff --git a/assets/icons.go b/assets/icons.go new file mode 100644 index 00000000..7ffa780d --- /dev/null +++ b/assets/icons.go @@ -0,0 +1,8 @@ +package assets + +import ( + _ "embed" +) + +//go:embed data/icons/pm_light_512.png +var PNG []byte diff --git a/assets/icons_default.go b/assets/icons_default.go new file mode 100644 index 00000000..2c1b6eb1 --- /dev/null +++ b/assets/icons_default.go @@ -0,0 +1,102 @@ +//go:build !windows + +package assets + +import ( + "bytes" + _ "embed" + "fmt" + "image" + "image/png" + + "github.com/safing/portbase/log" + "golang.org/x/image/draw" +) + +// Colored Icon IDs. +const ( + GreenID = 0 + YellowID = 1 + RedID = 2 + BlueID = 3 +) + +// Icons. +var ( + //go:embed data/icons/pm_light_green_512.png + GreenPNG []byte + + //go:embed data/icons/pm_light_yellow_512.png + YellowPNG []byte + + //go:embed data/icons/pm_light_red_512.png + RedPNG []byte + + //go:embed data/icons/pm_light_blue_512.png + BluePNG []byte + + // ColoredIcons holds all the icons as .PNGs + ColoredIcons [4][]byte +) + +func init() { + setColoredIcons() +} + +func setColoredIcons() { + ColoredIcons = [4][]byte{ + GreenID: GreenPNG, + YellowID: YellowPNG, + RedID: RedPNG, + BlueID: BluePNG, + } +} + +// ScaleColoredIconsTo scales all colored icons to the given size. +// It must be called before any colored icons are used. +// It does nothing on Windows. +func ScaleColoredIconsTo(pixelSize int) { + // Scale colored icons only. + GreenPNG = quickScalePNG(GreenPNG, pixelSize) + YellowPNG = quickScalePNG(YellowPNG, pixelSize) + RedPNG = quickScalePNG(RedPNG, pixelSize) + BluePNG = quickScalePNG(BluePNG, pixelSize) + + // Repopulate colored icons. + setColoredIcons() +} + +func quickScalePNG(imgData []byte, pixelSize int) []byte { + scaledImage, err := scalePNGTo(imgData, pixelSize) + if err != nil { + log.Warningf("failed to scale image (using original): %s", err) + return imgData + } + return scaledImage +} + +func scalePNGTo(imgData []byte, pixelSize int) ([]byte, error) { + img, err := png.Decode(bytes.NewReader(imgData)) + if err != nil { + return nil, fmt.Errorf("failed to decode image: %w", err) + } + + // Return data unprocessed if image already has the correct size. + if img.Bounds().Dx() == pixelSize { + return imgData, nil + } + + // Scale image to given size. + rectangle := image.Rect(0, 0, pixelSize, pixelSize) + scaledImage := image.NewRGBA(rectangle) + draw.CatmullRom.Scale(scaledImage, rectangle, img, img.Bounds(), draw.Over, nil) + + // Encode scaled image. + scaledImgBuffer := new(bytes.Buffer) + err = png.Encode(scaledImgBuffer, scaledImage) + if err != nil { + return nil, fmt.Errorf("failed to encode image: %w", err) + } + + return scaledImgBuffer.Bytes(), nil +} diff --git a/assets/icons_windows.go b/assets/icons_windows.go new file mode 100644 index 00000000..83f5db2e --- /dev/null +++ b/assets/icons_windows.go @@ -0,0 +1,41 @@ +package assets + +import ( + _ "embed" +) + +// Colored Icon IDs. +const ( + GreenID = 0 + YellowID = 1 + RedID = 2 + BlueID = 3 +) + +// Icons. +var ( + //go:embed data/icons/pm_light_green_512.ico + GreenICO []byte + + //go:embed data/icons/pm_light_yellow_512.ico + YellowICO []byte + + //go:embed data/icons/pm_light_red_512.ico + RedICO []byte + + //go:embed data/icons/pm_light_blue_512.ico + BlueICO []byte + + // ColoredIcons holds all the icons as .ICOs + ColoredIcons = [4][]byte{ + GreenID: GreenICO, + YellowID: YellowICO, + RedID: RedICO, + BlueID: BlueICO, + } +) + +// ScaleColoredIconsTo scales all colored icons to the given size. +// It must be called before any colored icons are used. +// It does nothing on Windows. +func ScaleColoredIconsTo(pixelSize int) {} diff --git a/cmds/notifier/.gitignore b/cmds/notifier/.gitignore new file mode 100644 index 00000000..602ad23c --- /dev/null +++ b/cmds/notifier/.gitignore @@ -0,0 +1,34 @@ +# Compiled binaries +notifier +notifier.exe + +# Go vendor +vendor + +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/cmds/notifier/README.md b/cmds/notifier/README.md new file mode 100644 index 00000000..bdfcece8 --- /dev/null +++ b/cmds/notifier/README.md @@ -0,0 +1,5 @@ +### Development Dependencies + +sudo apt install libgtk-3-dev libayatana-appindicator3-dev libwebkitgtk-3.0-dev libgl1-mesa-dev libglu1-mesa-dev libnotify-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev + +sudo pacman -S libappindicator-gtk3 diff --git a/cmds/notifier/http_api.go b/cmds/notifier/http_api.go new file mode 100644 index 00000000..bb0eebf3 --- /dev/null +++ b/cmds/notifier/http_api.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/cookiejar" + "strings" + "time" + + "github.com/safing/portbase/log" +) + +const ( + apiBaseURL = "http://127.0.0.1:817/api/v1/" + apiShutdownEndpoint = "core/shutdown" +) + +var ( + httpApiClient *http.Client +) + +func init() { + // Make cookie jar. + jar, err := cookiejar.New(nil) + if err != nil { + log.Warningf("http-api: failed to create cookie jar: %s", err) + jar = nil + } + + // Create client. + httpApiClient = &http.Client{ + Jar: jar, + Timeout: 3 * time.Second, + } +} + +func httpApiAction(endpoint string) (response string, err error) { + // Make action request. + resp, err := httpApiClient.Post(apiBaseURL+endpoint, "", nil) + if err != nil { + return "", fmt.Errorf("request failed: %w", err) + } + + // Read the response body. + defer resp.Body.Close() + respData, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("failed to read data: %w", err) + } + response = strings.TrimSpace(string(respData)) + + // Check if the request was successful on the server. + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + return response, fmt.Errorf("server failed with %s: %s", resp.Status, response) + } + + return response, nil +} + +// TriggerShutdown triggers a shutdown via the APi. +func TriggerShutdown() error { + _, err := httpApiAction(apiShutdownEndpoint) + return err +} diff --git a/cmds/notifier/icons.go b/cmds/notifier/icons.go new file mode 100644 index 00000000..93b3db74 --- /dev/null +++ b/cmds/notifier/icons.go @@ -0,0 +1,25 @@ +package main + +import ( + "os" + "path/filepath" + "sync" + + icons "github.com/safing/portmaster/assets" +) + +var ( + appIconEnsureOnce sync.Once + appIconPath string +) + +func ensureAppIcon() (location string, err error) { + appIconEnsureOnce.Do(func() { + if appIconPath == "" { + appIconPath = filepath.Join(dataDir, "exec", "portmaster.png") + } + err = os.WriteFile(appIconPath, icons.PNG, 0o0644) + }) + + return appIconPath, err +} diff --git a/cmds/notifier/main.go b/cmds/notifier/main.go new file mode 100644 index 00000000..8bd95b3b --- /dev/null +++ b/cmds/notifier/main.go @@ -0,0 +1,286 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "os" + "os/signal" + "path/filepath" + "runtime" + "runtime/pprof" + "strings" + "sync" + "syscall" + "time" + + "github.com/tevino/abool" + + "github.com/safing/portbase/api/client" + "github.com/safing/portbase/dataroot" + "github.com/safing/portbase/info" + "github.com/safing/portbase/log" + "github.com/safing/portbase/modules" + "github.com/safing/portbase/updater" + "github.com/safing/portbase/utils" + "github.com/safing/portmaster/service/updates/helper" +) + +var ( + dataDir string + printStackOnExit bool + showVersion bool + + apiClient = client.NewClient("127.0.0.1:817") + connected = abool.New() + shuttingDown = abool.New() + restarting = abool.New() + + mainCtx, cancelMainCtx = context.WithCancel(context.Background()) + mainWg = &sync.WaitGroup{} + + dataRoot *utils.DirStructure + // Create registry. + registry = &updater.ResourceRegistry{ + Name: "updates", + UpdateURLs: []string{ + "https://updates.safing.io", + }, + DevMode: false, + Online: false, // disable download of resources (this is job for the core). + } +) + +func init() { + flag.StringVar(&dataDir, "data", "", "set data directory") + flag.BoolVar(&printStackOnExit, "print-stack-on-exit", false, "prints the stack before of shutting down") + flag.BoolVar(&showVersion, "version", false, "show version and exit") + + runtime.GOMAXPROCS(2) +} + +func main() { + // parse flags + flag.Parse() + + // set meta info + info.Set("Portmaster Notifier", "0.3.6", "GPLv3", false) + + // check if meta info is ok + err := info.CheckVersion() + if err != nil { + fmt.Println("compile error: please compile using the provided build script") + os.Exit(1) + } + + // print help + if modules.HelpFlag { + flag.Usage() + os.Exit(0) + } + + if showVersion { + fmt.Println(info.FullVersion()) + os.Exit(0) + } + + // auto detect + if dataDir == "" { + dataDir = detectDataDir() + } + + // check data dir + if dataDir == "" { + fmt.Fprintln(os.Stderr, "please set the data directory using --data=/path/to/data/dir") + os.Exit(1) + } + + // switch to safe exec dir + err = os.Chdir(filepath.Join(dataDir, "exec")) + if err != nil { + fmt.Fprintf(os.Stderr, "warning: failed to switch to safe exec dir: %s\n", err) + } + + // start log writer + err = log.Start() + if err != nil { + fmt.Fprintf(os.Stderr, "failed to start logging: %s\n", err) + os.Exit(1) + } + + // load registry + err = configureRegistry(true) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to load registry: %s\n", err) + os.Exit(1) + } + + // connect to API + go apiClient.StayConnected() + go apiStatusMonitor() + + // start subsystems + go tray() + go subsystemsClient() + go spnStatusClient() + go notifClient() + go startShutdownEventListener() + + // Shutdown + // catch interrupt for clean shutdown + signalCh := make(chan os.Signal, 1) + signal.Notify( + signalCh, + os.Interrupt, + syscall.SIGHUP, + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGQUIT, + ) + + // wait for shutdown + select { + case <-signalCh: + fmt.Println(" <INTERRUPT>") + log.Warning("program was interrupted, shutting down") + case <-mainCtx.Done(): + log.Warning("program is shutting down") + } + + if printStackOnExit { + fmt.Println("=== PRINTING STACK ===") + _ = pprof.Lookup("goroutine").WriteTo(os.Stdout, 2) + fmt.Println("=== END STACK ===") + } + go func() { + time.Sleep(10 * time.Second) + fmt.Println("===== TAKING TOO LONG FOR SHUTDOWN - PRINTING STACK TRACES =====") + _ = pprof.Lookup("goroutine").WriteTo(os.Stdout, 2) + os.Exit(1) + }() + + // clear all notifications + clearNotifications() + + // shutdown + cancelMainCtx() + mainWg.Wait() + + apiClient.Shutdown() + exitTray() + log.Shutdown() + + os.Exit(0) +} + +func apiStatusMonitor() { + for { + // Wait for connection. + <-apiClient.Online() + connected.Set() + triggerTrayUpdate() + + // Wait for lost connection. + <-apiClient.Offline() + connected.UnSet() + triggerTrayUpdate() + } +} + +func detectDataDir() string { + // get path of executable + binPath, err := os.Executable() + if err != nil { + return "" + } + // get directory + binDir := filepath.Dir(binPath) + // check if we in the updates directory + identifierDir := filepath.Join("updates", runtime.GOOS+"_"+runtime.GOARCH, "notifier") + // check if there is a match and return data dir + if strings.HasSuffix(binDir, identifierDir) { + return filepath.Clean(strings.TrimSuffix(binDir, identifierDir)) + } + return "" +} + +func configureRegistry(mustLoadIndex bool) error { + // If dataDir is not set, check the environment variable. + if dataDir == "" { + dataDir = os.Getenv("PORTMASTER_DATA") + } + + // If it's still empty, try to auto-detect it. + if dataDir == "" { + dataDir = detectInstallationDir() + } + + // Finally, if it's still empty, the user must provide it. + if dataDir == "" { + return errors.New("please set the data directory using --data=/path/to/data/dir") + } + + // Remove left over quotes. + dataDir = strings.Trim(dataDir, `\"`) + // Initialize data root. + err := dataroot.Initialize(dataDir, 0o0755) + if err != nil { + return fmt.Errorf("failed to initialize data root: %w", err) + } + dataRoot = dataroot.Root() + + // Initialize registry. + err = registry.Initialize(dataRoot.ChildDir("updates", 0o0755)) + if err != nil { + return err + } + + return updateRegistryIndex(mustLoadIndex) +} + +func detectInstallationDir() string { + exePath, err := filepath.Abs(os.Args[0]) + if err != nil { + return "" + } + + parent := filepath.Dir(exePath) // parent should be "...\updates\windows_amd64\notifier" + stableJSONFile := filepath.Join(parent, "..", "..", "stable.json") // "...\updates\stable.json" + stat, err := os.Stat(stableJSONFile) + if err != nil { + return "" + } + + if stat.IsDir() { + return "" + } + + return parent +} + +func updateRegistryIndex(mustLoadIndex bool) error { + // Set indexes based on the release channel. + warning := helper.SetIndexes(registry, "", false, false, false) + if warning != nil { + log.Warningf("%q", warning) + } + + // Load indexes from disk or network, if needed and desired. + err := registry.LoadIndexes(context.Background()) + if err != nil { + log.Warningf("error loading indexes %q", warning) + if mustLoadIndex { + return err + } + } + + // Load versions from disk to know which others we have and which are available. + err = registry.ScanStorage("") + if err != nil { + log.Warningf("error during storage scan: %q\n", err) + } + + registry.SelectVersions() + return nil +} diff --git a/cmds/notifier/notification.go b/cmds/notifier/notification.go new file mode 100644 index 00000000..fc37690b --- /dev/null +++ b/cmds/notifier/notification.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + + pbnotify "github.com/safing/portbase/notifications" +) + +// Notification represents a notification that is to be delivered to the user. +type Notification struct { + pbnotify.Notification + + // systemID holds the ID returned by the dbus interface on Linux or by WinToast library on Windows. + systemID NotificationID +} + +// IsSupported returns whether the action is supported on this system. +func IsSupportedAction(a pbnotify.Action) bool { + switch a.Type { + case pbnotify.ActionTypeNone: + return true + default: + return false + } +} + +// SelectAction sends an action back to the portmaster. +func (n *Notification) SelectAction(action string) { + new := &pbnotify.Notification{ + EventID: n.EventID, + SelectedActionID: action, + } + + // FIXME: check response + apiClient.Update(fmt.Sprintf("%s%s", dbNotifBasePath, new.EventID), new, nil) +} diff --git a/cmds/notifier/notify.go b/cmds/notifier/notify.go new file mode 100644 index 00000000..1b271b67 --- /dev/null +++ b/cmds/notifier/notify.go @@ -0,0 +1,103 @@ +package main + +import ( + "fmt" + "strings" + "sync" + "time" + + "github.com/safing/portbase/api/client" + "github.com/safing/portbase/formats/dsd" + "github.com/safing/portbase/log" + + pbnotify "github.com/safing/portbase/notifications" +) + +const ( + dbNotifBasePath = "notifications:all/" +) + +var ( + notifications = make(map[string]*Notification) + notificationsLock sync.Mutex +) + +func notifClient() { + notifOp := apiClient.Qsub(fmt.Sprintf("query %s where ShowOnSystem is true", dbNotifBasePath), handleNotification) + notifOp.EnableResuscitation() + + // start the action listener and block + // until it's closed. + actionListener() +} + +func handleNotification(m *client.Message) { + notificationsLock.Lock() + defer notificationsLock.Unlock() + + log.Tracef("received %s msg: %s", m.Type, m.Key) + + switch m.Type { + case client.MsgError: + case client.MsgDone: + case client.MsgSuccess: + case client.MsgOk, client.MsgUpdate, client.MsgNew: + + n := &Notification{} + _, err := dsd.Load(m.RawValue, &n.Notification) + if err != nil { + log.Warningf("notify: failed to parse new notification: %s", err) + return + } + + // copy existing system values + existing, ok := notifications[n.EventID] + if ok { + existing.Lock() + n.systemID = existing.systemID + existing.Unlock() + } + + // save + notifications[n.EventID] = n + + // Handle notification. + switch { + case existing != nil: + // Cancel existing notification if not active, else ignore. + if n.State != pbnotify.Active { + existing.Cancel() + } + return + case n.State == pbnotify.Active: + // Show new notifications that are active. + n.Show() + default: + // Ignore new notifications that are not active. + } + + case client.MsgDelete: + + n, ok := notifications[strings.TrimPrefix(m.Key, dbNotifBasePath)] + if ok { + n.Cancel() + delete(notifications, n.EventID) + } + + case client.MsgWarning: + case client.MsgOffline: + } +} + +func clearNotifications() { + notificationsLock.Lock() + defer notificationsLock.Unlock() + + for _, n := range notifications { + n.Cancel() + } + + // Wait for goroutines that cancel notifications. + // TODO: Revamp to use a waitgroup. + time.Sleep(1 * time.Second) +} diff --git a/cmds/notifier/notify_linux.go b/cmds/notifier/notify_linux.go new file mode 100644 index 00000000..bcf650cf --- /dev/null +++ b/cmds/notifier/notify_linux.go @@ -0,0 +1,154 @@ +package main + +import ( + "context" + "sync" + + notify "github.com/dhaavi/go-notify" + "github.com/safing/portbase/log" +) + +type NotificationID uint32 + +var ( + capabilities notify.Capabilities + notifsByID sync.Map +) + +func init() { + var err error + capabilities, err = notify.GetCapabilities() + if err != nil { + log.Errorf("failed to get notification system capabilities: %s", err) + } +} + +func handleActions(ctx context.Context, actions chan notify.Signal) { + mainWg.Add(1) + defer mainWg.Done() + +listenForNotifications: + for { + select { + case <-ctx.Done(): + return + case sig := <-actions: + if sig.Name != "org.freedesktop.Notifications.ActionInvoked" { + // we don't care for anything else (dismissed, closed) + continue listenForNotifications + } + + // get notification by system ID + n, ok := notifsByID.LoadAndDelete(NotificationID(sig.ID)) + + if !ok { + continue listenForNotifications + } + + notification := n.(*Notification) + + log.Tracef("notify: received signal: %+v", sig) + if sig.ActionKey != "" { + // send action + if ok { + notification.Lock() + notification.SelectAction(sig.ActionKey) + notification.Unlock() + } + } else { + log.Tracef("notify: notification clicked: %+v", sig) + // Global action invoked, start the app + launchApp() + } + } + } + +} + +func actionListener() { + actions := make(chan notify.Signal, 100) + + go handleActions(mainCtx, actions) + + err := notify.SignalNotify(mainCtx, actions) + if err != nil && err != context.Canceled { + log.Errorf("notify: signal listener failed: %s", err) + } +} + +// Show shows the notification. +func (n *Notification) Show() { + sysN := notify.NewNotification("Portmaster", n.Message) + // see https://developer.gnome.org/notification-spec/ + + // The optional name of the application sending the notification. + // Can be blank. + sysN.AppName = "Portmaster" + + // The optional notification ID that this notification replaces. + sysN.ReplacesID = uint32(n.systemID) + + // The optional program icon of the calling application. + // sysN.AppIcon string + + // The summary text briefly describing the notification. + // Summary string (arg 1) + + // The optional detailed body text. + // Body string (arg 2) + + // The actions send a request message back to the notification client + // when invoked. + // sysN.Actions []string + if capabilities.Actions { + sysN.Actions = make([]string, 0, len(n.AvailableActions)*2) + for _, action := range n.AvailableActions { + if IsSupportedAction(*action) { + sysN.Actions = append(sysN.Actions, action.ID) + sysN.Actions = append(sysN.Actions, action.Text) + } + } + } + + // Set Portmaster icon. + iconLocation, err := ensureAppIcon() + if err != nil { + log.Warningf("notify: failed to write icon: %s", err) + } + sysN.AppIcon = iconLocation + + // TODO: Use hints to display icon of affected app. + // Hints are a way to provide extra data to a notification server. + // sysN.Hints = make(map[string]interface{}) + + // The timeout time in milliseconds since the display of the + // notification at which the notification should automatically close. + // sysN.Timeout int32 + + newID, err := sysN.Show() + if err != nil { + log.Warningf("notify: failed to show notification %s", n.EventID) + return + } + + notifsByID.Store(NotificationID(newID), n) + + n.Lock() + defer n.Unlock() + n.systemID = NotificationID(newID) +} + +// Cancel cancels the notification. +func (n *Notification) Cancel() { + n.Lock() + defer n.Unlock() + + // TODO: could a ID of 0 be valid? + if n.systemID != 0 { + err := notify.CloseNotification(uint32(n.systemID)) + if err != nil { + log.Warningf("notify: failed to close notification %s/%d", n.EventID, n.systemID) + } + notifsByID.Delete(n.systemID) + } +} diff --git a/cmds/notifier/notify_windows.go b/cmds/notifier/notify_windows.go new file mode 100644 index 00000000..abb56be0 --- /dev/null +++ b/cmds/notifier/notify_windows.go @@ -0,0 +1,184 @@ +package main + +import ( + "fmt" + "sync" + + "github.com/safing/portbase/log" + "github.com/safing/portmaster/cmds/notifier/wintoast" + "github.com/safing/portmaster/service/updates/helper" +) + +type NotificationID int64 + +const ( + appName = "Portmaster" + appUserModelID = "io.safing.portmaster.2" + originalShortcutPath = "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Portmaster\\Portmaster.lnk" +) + +const ( + SoundDefault = 0 + SoundSilent = 1 + SoundLoop = 2 +) + +const ( + SoundPathDefault = 0 + // see notification_glue.h if you need more types +) + +var ( + initOnce sync.Once + lib *wintoast.WinToast + notificationsByIDs sync.Map +) + +func getLib() *wintoast.WinToast { + initOnce.Do(func() { + dllPath, err := getDllPath() + if err != nil { + log.Errorf("notify: failed to get dll path: %s", err) + return + } + // Load dll and all the functions + newLib, err := wintoast.New(dllPath) + if err != nil { + log.Errorf("notify: failed to load library: %s", err) + return + } + + // Initialize. This will create or update application shortcut. C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs + // and it will be of the originalShortcutPath with no CLSID and different AUMI + err = newLib.Initialize(appName, appUserModelID, originalShortcutPath) + if err != nil { + log.Errorf("notify: failed to load library: %s", err) + return + } + + // library was initialized successfully + lib = newLib + + // Set callbacks + + err = lib.SetCallbacks(notificationActivatedCallback, notificationDismissedCallback, notificationDismissedCallback) + if err != nil { + log.Warningf("notify: failed to set callbacks: %s", err) + return + } + }) + + return lib +} + +// Show shows the notification. +func (n *Notification) Show() { + // Lock notification + n.Lock() + defer n.Unlock() + + // Create new notification object + builder, err := getLib().NewNotification(n.Title, n.Message) + if err != nil { + log.Errorf("notify: failed to create notification: %s", err) + return + } + // Make sure memory is freed when done + defer builder.Delete() + + // if needed set notification icon + // _ = builder.SetImage(iconLocation) + + // Leaving the default value for the sound + // _ = builder.SetSound(SoundDefault, SoundPathDefault) + + // Set all the required actions. + for _, action := range n.AvailableActions { + err = builder.AddButton(action.Text) + if err != nil { + log.Warningf("notify: failed to add button: %s", err) + } + } + + // Show notification. + id, err := builder.Show() + if err != nil { + log.Errorf("notify: failed to show notification: %s", err) + return + } + n.systemID = NotificationID(id) + + // Link system id to the notification object + notificationsByIDs.Store(NotificationID(id), n) + + log.Debugf("notify: showing notification %q: %d", n.Title, n.systemID) +} + +// Cancel cancels the notification. +func (n *Notification) Cancel() { + // Lock notification + n.Lock() + defer n.Unlock() + + // No need to check for errors. If it fails it is probably already dismissed + _ = getLib().HideNotification(int64(n.systemID)) + + notificationsByIDs.Delete(n.systemID) + log.Debugf("notify: notification canceled %q: %d", n.Title, n.systemID) +} + +func notificationActivatedCallback(id int64, actionIndex int32) { + if actionIndex == -1 { + // The user clicked on the notification (not a button), open the portmaster and delete + launchApp() + notificationsByIDs.Delete(NotificationID(id)) + log.Debugf("notify: notification clicked %d", id) + return + } + + // The user click one of the buttons + + // Get notified object + n, ok := notificationsByIDs.LoadAndDelete(NotificationID(id)) + if !ok { + return + } + + notification := n.(*Notification) + + notification.Lock() + defer notification.Unlock() + + // Set selected action + actionID := notification.AvailableActions[actionIndex].ID + notification.SelectAction(actionID) + + log.Debugf("notify: notification button cliecked %d button id: %d", id, actionIndex) +} + +func notificationDismissedCallback(id int64, reason int32) { + // Failure or user dismissed the notification + if reason == 0 { + notificationsByIDs.Delete(NotificationID(id)) + log.Debugf("notify: notification dissmissed %d", id) + } +} + +func getDllPath() (string, error) { + if dataDir == "" { + return "", fmt.Errorf("dataDir is empty") + } + + // Aks the registry for the dll path + identifier := helper.PlatformIdentifier("notifier/portmaster-wintoast.dll") + file, err := registry.GetFile(identifier) + if err != nil { + return "", err + } + return file.Path(), nil +} + +func actionListener() { + // initialize the library + _ = getLib() +} diff --git a/cmds/notifier/shutdown.go b/cmds/notifier/shutdown.go new file mode 100644 index 00000000..f943938d --- /dev/null +++ b/cmds/notifier/shutdown.go @@ -0,0 +1,50 @@ +package main + +import ( + "github.com/safing/portbase/api/client" + "github.com/safing/portbase/log" +) + +func startShutdownEventListener() { + shutdownNotifOp := apiClient.Sub("query runtime:modules/core/event/shutdown", handleShutdownEvent) + shutdownNotifOp.EnableResuscitation() + + restartNotifOp := apiClient.Sub("query runtime:modules/core/event/restart", handleRestartEvent) + restartNotifOp.EnableResuscitation() +} + +func handleShutdownEvent(m *client.Message) { + switch m.Type { + case client.MsgOk, client.MsgUpdate, client.MsgNew: + shuttingDown.Set() + triggerTrayUpdate() + + log.Warningf("shutdown: received shutdown event, shutting down now") + + // wait for the API client connection to die + <-apiClient.Offline() + shuttingDown.UnSet() + + cancelMainCtx() + + case client.MsgWarning, client.MsgError: + log.Errorf("shutdown: event subscription error: %s", string(m.RawValue)) + } +} + +func handleRestartEvent(m *client.Message) { + switch m.Type { + case client.MsgOk, client.MsgUpdate, client.MsgNew: + restarting.Set() + triggerTrayUpdate() + + log.Warningf("restart: received restart event") + + // wait for the API client connection to die + <-apiClient.Offline() + restarting.UnSet() + triggerTrayUpdate() + case client.MsgWarning, client.MsgError: + log.Errorf("shutdown: event subscription error: %s", string(m.RawValue)) + } +} diff --git a/cmds/notifier/snoretoast-guid.patch b/cmds/notifier/snoretoast-guid.patch new file mode 100644 index 00000000..1a050e5f --- /dev/null +++ b/cmds/notifier/snoretoast-guid.patch @@ -0,0 +1,15 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 498226a..446ba5e 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -2,7 +2,9 @@ cmake_minimum_required(VERSION 3.4) + + project(snoretoast VERSION 0.6.0) + # Always change the guid when the version is changed SNORETOAST_CALLBACK_GUID +-set(SNORETOAST_CALLBACK_GUID eb1fdd5b-8f70-4b5a-b230-998a2dc19303) ++#We keep it fixed! ++set(SNORETOAST_CALLBACK_GUID 7F00FB48-65D5-4BA8-A35B-F194DA7E1A51) ++ + + set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/) + diff --git a/cmds/notifier/spn.go b/cmds/notifier/spn.go new file mode 100644 index 00000000..1da5639d --- /dev/null +++ b/cmds/notifier/spn.go @@ -0,0 +1,103 @@ +package main + +import ( + "sync" + "time" + + "github.com/safing/portbase/api/client" + "github.com/safing/portbase/formats/dsd" + "github.com/safing/portbase/log" + "github.com/tevino/abool" +) + +const ( + spnModuleKey = "config:spn/enable" + spnStatusKey = "runtime:spn/status" +) + +var ( + spnEnabled = abool.New() + + spnStatusCache *SPNStatus + spnStatusCacheLock sync.Mutex +) + +// SPNStatus holds SPN status information. +type SPNStatus struct { + Status string + HomeHubID string + HomeHubName string + ConnectedIP string + ConnectedTransport string + ConnectedSince *time.Time +} + +// GetSPNStatus returns the SPN status. +func GetSPNStatus() *SPNStatus { + spnStatusCacheLock.Lock() + defer spnStatusCacheLock.Unlock() + + return spnStatusCache +} + +func updateSPNStatus(s *SPNStatus) { + spnStatusCacheLock.Lock() + defer spnStatusCacheLock.Unlock() + + spnStatusCache = s +} + +func spnStatusClient() { + moduleQueryOp := apiClient.Qsub("query "+spnModuleKey, handleSPNModuleUpdate) + moduleQueryOp.EnableResuscitation() + + statusQueryOp := apiClient.Qsub("query "+spnStatusKey, handleSPNStatusUpdate) + statusQueryOp.EnableResuscitation() +} + +func handleSPNModuleUpdate(m *client.Message) { + switch m.Type { + case client.MsgOk, client.MsgUpdate, client.MsgNew: + var cfg struct { + Value bool `json:"Value"` + } + _, err := dsd.Load(m.RawValue, &cfg) + if err != nil { + log.Warningf("config: failed to parse config: %s", err) + return + } + log.Infof("config: received update to SPN module: enabled=%v", cfg.Value) + + spnEnabled.SetTo(cfg.Value) + triggerTrayUpdate() + + default: + } +} + +func handleSPNStatusUpdate(m *client.Message) { + switch m.Type { + case client.MsgOk, client.MsgUpdate, client.MsgNew: + newStatus := &SPNStatus{} + _, err := dsd.Load(m.RawValue, newStatus) + if err != nil { + log.Warningf("config: failed to parse config: %s", err) + return + } + log.Infof("config: received update to SPN status: %+v", newStatus) + + updateSPNStatus(newStatus) + triggerTrayUpdate() + + default: + } +} + +func ToggleSPN() { + var cfg struct { + Value bool `json:"Value"` + } + cfg.Value = !spnEnabled.IsSet() + + apiClient.Update(spnModuleKey, &cfg, nil) +} diff --git a/cmds/notifier/subsystems.go b/cmds/notifier/subsystems.go new file mode 100644 index 00000000..f810538c --- /dev/null +++ b/cmds/notifier/subsystems.go @@ -0,0 +1,122 @@ +package main + +import ( + "fmt" + "sync" + + "github.com/safing/portbase/api/client" + "github.com/safing/portbase/formats/dsd" + "github.com/safing/portbase/log" +) + +const ( + subsystemsKeySpace = "runtime:subsystems/" + + // Module Failure Status Values + // FailureNone = 0 // unused + // FailureHint = 1 // unused + FailureWarning = 2 + FailureError = 3 +) + +var ( + subsystems = make(map[string]*Subsystem) + subsystemsLock sync.Mutex +) + +// Subsystem describes a subset of modules that represent a part of a +// service or program to the user. Subsystems can be (de-)activated causing +// all related modules to be brought down or up. +type Subsystem struct { //nolint:maligned // not worth the effort + // ID is a unique identifier for the subsystem. + ID string + + // Name holds a human readable name of the subsystem. + Name string + + // Description may holds an optional description of + // the subsystem's purpose. + Description string + + // Modules contains all modules that are related to the subsystem. + // Note that this slice also contains a reference to the subsystem + // module itself. + Modules []*ModuleStatus + + // FailureStatus is the worst failure status that is currently + // set in one of the subsystem's dependencies. + FailureStatus uint8 +} + +// ModuleStatus describes the status of a module. +type ModuleStatus struct { + Name string + Enabled bool + Status uint8 + FailureStatus uint8 + FailureID string + FailureMsg string +} + +// GetFailure returns the worst of all subsystem failures. +func GetFailure() (failureStatus uint8, failureMsg string) { + subsystemsLock.Lock() + defer subsystemsLock.Unlock() + + for _, subsystem := range subsystems { + for _, module := range subsystem.Modules { + if failureStatus < module.FailureStatus { + failureStatus = module.FailureStatus + failureMsg = module.FailureMsg + } + } + } + + return +} + +func updateSubsystem(s *Subsystem) { + subsystemsLock.Lock() + defer subsystemsLock.Unlock() + + subsystems[s.ID] = s +} + +func clearSubsystems() { + subsystemsLock.Lock() + defer subsystemsLock.Unlock() + + for key := range subsystems { + delete(subsystems, key) + } +} + +func subsystemsClient() { + subsystemsOp := apiClient.Qsub(fmt.Sprintf("query %s", subsystemsKeySpace), handleSubsystem) + subsystemsOp.EnableResuscitation() +} + +func handleSubsystem(m *client.Message) { + switch m.Type { + case client.MsgError: + case client.MsgDone: + case client.MsgSuccess: + case client.MsgOk, client.MsgUpdate, client.MsgNew: + + newSubsystem := &Subsystem{} + _, err := dsd.Load(m.RawValue, newSubsystem) + if err != nil { + log.Warningf("subsystems: failed to parse new subsystem: %s", err) + return + } + updateSubsystem(newSubsystem) + triggerTrayUpdate() + + case client.MsgDelete: + case client.MsgWarning: + case client.MsgOffline: + + clearSubsystems() + + } +} diff --git a/cmds/notifier/tray.go b/cmds/notifier/tray.go new file mode 100644 index 00000000..5766611f --- /dev/null +++ b/cmds/notifier/tray.go @@ -0,0 +1,218 @@ +package main + +import ( + "flag" + "os/exec" + "path/filepath" + "runtime" + "strings" + "sync" + "time" + + "fyne.io/systray" + + "github.com/safing/portbase/log" + icons "github.com/safing/portmaster/assets" +) + +const ( + shortenStatusMsgTo = 40 +) + +var ( + trayLock sync.Mutex + + scaleColoredIconsTo int + + activeIconID int = -1 + activeStatusMsg = "" + activeSPNStatus = "" + activeSPNSwitch = "" + + menuItemStatusMsg *systray.MenuItem + menuItemSPNStatus *systray.MenuItem + menuItemSPNSwitch *systray.MenuItem +) + +func init() { + flag.IntVar(&scaleColoredIconsTo, "scale-icons", 32, "scale colored icons to given size in pixels") + + // lock until ready + trayLock.Lock() +} + +func tray() { + if scaleColoredIconsTo > 0 { + icons.ScaleColoredIconsTo(scaleColoredIconsTo) + } + + systray.Run(onReady, onExit) +} + +func exitTray() { + systray.Quit() +} + +func onReady() { + // unlock when ready + defer trayLock.Unlock() + + // icon + systray.SetIcon(icons.ColoredIcons[icons.RedID]) + if runtime.GOOS == "windows" { + // systray.SetTitle("Portmaster Notifier") // Don't set title, as it may be displayed in full in the menu/tray bar. (Ubuntu) + systray.SetTooltip("Portmaster Notifier") + } + + // menu: open app + if dataDir != "" { + menuItemOpenApp := systray.AddMenuItem("Open App", "") + go clickListener(menuItemOpenApp, launchApp) + systray.AddSeparator() + } + + // menu: status + + menuItemStatusMsg = systray.AddMenuItem("Loading...", "") + menuItemStatusMsg.Disable() + systray.AddSeparator() + + // menu: SPN + + menuItemSPNStatus = systray.AddMenuItem("Loading...", "") + menuItemSPNStatus.Disable() + menuItemSPNSwitch = systray.AddMenuItem("Loading...", "") + go clickListener(menuItemSPNSwitch, func() { + ToggleSPN() + }) + systray.AddSeparator() + + // menu: quit + systray.AddSeparator() + closeTray := systray.AddMenuItem("Close Tray Notifier", "") + go clickListener(closeTray, func() { + cancelMainCtx() + }) + shutdownPortmaster := systray.AddMenuItem("Shut Down Portmaster", "") + go clickListener(shutdownPortmaster, func() { + _ = TriggerShutdown() + time.Sleep(1 * time.Second) + cancelMainCtx() + }) +} + +func onExit() { + +} + +func triggerTrayUpdate() { + // TODO: Deduplicate triggers. + go updateTray() +} + +// updateTray update the state of the tray depending on the currently available information. +func updateTray() { + // Get current information. + spnStatus := GetSPNStatus() + failureID, failureMsg := GetFailure() + + trayLock.Lock() + defer trayLock.Unlock() + + // Select icon and status message to show. + newIconID := icons.GreenID + newStatusMsg := "Secure" + switch { + case shuttingDown.IsSet(): + newIconID = icons.RedID + newStatusMsg = "Shutting Down Portmaster" + + case restarting.IsSet(): + newIconID = icons.YellowID + newStatusMsg = "Restarting Portmaster" + + case !connected.IsSet(): + newIconID = icons.RedID + newStatusMsg = "Waiting for Portmaster Core Service" + + case failureID == FailureError: + newIconID = icons.RedID + newStatusMsg = failureMsg + + case failureID == FailureWarning: + newIconID = icons.YellowID + newStatusMsg = failureMsg + + case spnEnabled.IsSet(): + newIconID = icons.BlueID + } + + // Set icon if changed. + if newIconID != activeIconID { + activeIconID = newIconID + systray.SetIcon(icons.ColoredIcons[activeIconID]) + } + + // Set message if changed. + if newStatusMsg != activeStatusMsg { + activeStatusMsg = newStatusMsg + + // Shorten message if too long. + shortenedMsg := activeStatusMsg + if len(shortenedMsg) > shortenStatusMsgTo && strings.Contains(shortenedMsg, ". ") { + shortenedMsg = strings.SplitN(shortenedMsg, ". ", 2)[0] + } + if len(shortenedMsg) > shortenStatusMsgTo { + shortenedMsg = shortenedMsg[:shortenStatusMsgTo] + "..." + } + + menuItemStatusMsg.SetTitle("Status: " + shortenedMsg) + } + + // Set SPN status if changed. + if spnStatus != nil && activeSPNStatus != spnStatus.Status { + activeSPNStatus = spnStatus.Status + menuItemSPNStatus.SetTitle("SPN: " + strings.Title(activeSPNStatus)) + } + + // Set SPN switch if changed. + newSPNSwitch := "Enable SPN" + if spnEnabled.IsSet() { + newSPNSwitch = "Disable SPN" + } + if activeSPNSwitch != newSPNSwitch { + activeSPNSwitch = newSPNSwitch + menuItemSPNSwitch.SetTitle(activeSPNSwitch) + } +} + +func clickListener(item *systray.MenuItem, fn func()) { + for range item.ClickedCh { + fn() + } +} + +func launchApp() { + // build path to app + pmStartPath := filepath.Join(dataDir, "portmaster-start") + if runtime.GOOS == "windows" { + pmStartPath += ".exe" + } + + // start app + cmd := exec.Command(pmStartPath, "app", "--data", dataDir) + err := cmd.Start() + if err != nil { + log.Warningf("failed to start app: %s", err) + return + } + + // Use cmd.Wait() instead of cmd.Process.Release() to properly release its resources. + // See https://github.com/golang/go/issues/36534 + go func() { + err := cmd.Wait() + if err != nil { + log.Warningf("failed to wait/release app process: %s", err) + } + }() +} diff --git a/cmds/notifier/wintoast/notification_builder.go b/cmds/notifier/wintoast/notification_builder.go new file mode 100644 index 00000000..89eca798 --- /dev/null +++ b/cmds/notifier/wintoast/notification_builder.go @@ -0,0 +1,90 @@ +//go:build windows + +package wintoast + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +type NotificationBuilder struct { + templatePointer uintptr + lib *WinToast +} + +func newNotification(lib *WinToast, title string, message string) (*NotificationBuilder, error) { + lib.Lock() + defer lib.Unlock() + + titleUTF, _ := windows.UTF16PtrFromString(title) + messageUTF, _ := windows.UTF16PtrFromString(message) + titleP := unsafe.Pointer(titleUTF) + messageP := unsafe.Pointer(messageUTF) + + ptr, _, err := lib.createNotification.Call(uintptr(titleP), uintptr(messageP)) + if ptr == 0 { + return nil, err + } + + return &NotificationBuilder{ptr, lib}, nil +} + +func (n *NotificationBuilder) Delete() { + if n == nil { + return + } + + n.lib.Lock() + defer n.lib.Unlock() + + _, _, _ = n.lib.deleteNotification.Call(n.templatePointer) +} + +func (n *NotificationBuilder) AddButton(text string) error { + n.lib.Lock() + defer n.lib.Unlock() + textUTF, _ := windows.UTF16PtrFromString(text) + textP := unsafe.Pointer(textUTF) + + rc, _, err := n.lib.addButton.Call(n.templatePointer, uintptr(textP)) + if rc != 1 { + return err + } + return nil +} + +func (n *NotificationBuilder) SetImage(iconPath string) error { + n.lib.Lock() + defer n.lib.Unlock() + pathUTF, _ := windows.UTF16PtrFromString(iconPath) + pathP := unsafe.Pointer(pathUTF) + + rc, _, err := n.lib.setImage.Call(n.templatePointer, uintptr(pathP)) + if rc != 1 { + return err + } + return nil +} + +func (n *NotificationBuilder) SetSound(option int, path int) error { + n.lib.Lock() + defer n.lib.Unlock() + + rc, _, err := n.lib.setSound.Call(n.templatePointer, uintptr(option), uintptr(path)) + if rc != 1 { + return err + } + return nil +} + +func (n *NotificationBuilder) Show() (int64, error) { + n.lib.Lock() + defer n.lib.Unlock() + + id, _, err := n.lib.showNotification.Call(n.templatePointer) + if int64(id) == -1 { + return -1, err + } + return int64(id), nil +} diff --git a/cmds/notifier/wintoast/wintoast.go b/cmds/notifier/wintoast/wintoast.go new file mode 100644 index 00000000..5d9a3380 --- /dev/null +++ b/cmds/notifier/wintoast/wintoast.go @@ -0,0 +1,217 @@ +//go:build windows + +package wintoast + +import ( + "fmt" + "sync" + "unsafe" + + "github.com/tevino/abool" + + "golang.org/x/sys/windows" +) + +// WinNotify holds the DLL handle. +type WinToast struct { + sync.RWMutex + + dll *windows.DLL + + initialized *abool.AtomicBool + + initialize *windows.Proc + isInitialized *windows.Proc + createNotification *windows.Proc + deleteNotification *windows.Proc + addButton *windows.Proc + setImage *windows.Proc + setSound *windows.Proc + showNotification *windows.Proc + hideNotification *windows.Proc + setActivatedCallback *windows.Proc + setDismissedCallback *windows.Proc + setFailedCallback *windows.Proc +} + +func New(dllPath string) (*WinToast, error) { + if dllPath == "" { + return nil, fmt.Errorf("winnotifiy: path to dll not specified") + } + + libraryObject := &WinToast{} + libraryObject.initialized = abool.New() + + // load dll + var err error + libraryObject.dll, err = windows.LoadDLL(dllPath) + if err != nil { + return nil, fmt.Errorf("winnotifiy: failed to load notifier dll %w", err) + } + + // load functions + libraryObject.initialize, err = libraryObject.dll.FindProc("PortmasterToastInitialize") + if err != nil { + return nil, fmt.Errorf("winnotifiy: PortmasterToastInitialize not found %w", err) + } + + libraryObject.isInitialized, err = libraryObject.dll.FindProc("PortmasterToastIsInitialized") + if err != nil { + return nil, fmt.Errorf("winnotifiy: PortmasterToastIsInitialized not found %w", err) + } + + libraryObject.createNotification, err = libraryObject.dll.FindProc("PortmasterToastCreateNotification") + if err != nil { + return nil, fmt.Errorf("winnotifiy: PortmasterToastCreateNotification not found %w", err) + } + + libraryObject.deleteNotification, err = libraryObject.dll.FindProc("PortmasterToastDeleteNotification") + if err != nil { + return nil, fmt.Errorf("winnotifiy: PortmasterToastDeleteNotification not found %w", err) + } + + libraryObject.addButton, err = libraryObject.dll.FindProc("PortmasterToastAddButton") + if err != nil { + return nil, fmt.Errorf("winnotifiy: PortmasterToastAddButton not found %w", err) + } + + libraryObject.setImage, err = libraryObject.dll.FindProc("PortmasterToastSetImage") + if err != nil { + return nil, fmt.Errorf("winnotifiy: PortmasterToastSetImage not found %w", err) + } + + libraryObject.setSound, err = libraryObject.dll.FindProc("PortmasterToastSetSound") + if err != nil { + return nil, fmt.Errorf("winnotifiy: PortmasterToastSetSound not found %w", err) + } + + libraryObject.showNotification, err = libraryObject.dll.FindProc("PortmasterToastShow") + if err != nil { + return nil, fmt.Errorf("winnotifiy: PortmasterToastShow not found %w", err) + } + + libraryObject.setActivatedCallback, err = libraryObject.dll.FindProc("PortmasterToastActivatedCallback") + if err != nil { + return nil, fmt.Errorf("winnotifiy: PortmasterActivatedCallback not found %w", err) + } + + libraryObject.setDismissedCallback, err = libraryObject.dll.FindProc("PortmasterToastDismissedCallback") + if err != nil { + return nil, fmt.Errorf("winnotifiy: PortmasterToastDismissedCallback not found %w", err) + } + + libraryObject.setFailedCallback, err = libraryObject.dll.FindProc("PortmasterToastFailedCallback") + if err != nil { + return nil, fmt.Errorf("winnotifiy: PortmasterToastFailedCallback not found %w", err) + } + + libraryObject.hideNotification, err = libraryObject.dll.FindProc("PortmasterToastHide") + if err != nil { + return nil, fmt.Errorf("winnotifiy: PortmasterToastHide not found %w", err) + } + + return libraryObject, nil +} + +func (lib *WinToast) Initialize(appName, aumi, originalShortcutPath string) error { + if lib == nil { + return fmt.Errorf("wintoast: lib object was nil") + } + + lib.Lock() + defer lib.Unlock() + + // Initialize all necessary string for the notification meta data + appNameUTF, _ := windows.UTF16PtrFromString(appName) + aumiUTF, _ := windows.UTF16PtrFromString(aumi) + linkUTF, _ := windows.UTF16PtrFromString(originalShortcutPath) + + // They are needed as unsafe pointers + appNameP := unsafe.Pointer(appNameUTF) + aumiP := unsafe.Pointer(aumiUTF) + linkP := unsafe.Pointer(linkUTF) + + // Initialize notifications + rc, _, err := lib.initialize.Call(uintptr(appNameP), uintptr(aumiP), uintptr(linkP)) + if rc != 0 { + return fmt.Errorf("wintoast: failed to initialize library rc = %d, %w", rc, err) + } + + // Check if if the initialization was successfully + rc, _, _ = lib.isInitialized.Call() + if rc == 1 { + lib.initialized.Set() + } else { + return fmt.Errorf("wintoast: initialized flag was not set: rc = %d", rc) + } + + return nil +} + +func (lib *WinToast) SetCallbacks(activated func(id int64, actionIndex int32), dismissed func(id int64, reason int32), failed func(id int64, reason int32)) error { + if lib == nil { + return fmt.Errorf("wintoast: lib object was nil") + } + + if lib.initialized.IsNotSet() { + return fmt.Errorf("winnotifiy: library not initialized") + } + + // Initialize notification activated callback + callback := windows.NewCallback(func(id int64, actionIndex int32) uint64 { + activated(id, actionIndex) + return 0 + }) + rc, _, err := lib.setActivatedCallback.Call(callback) + if rc != 1 { + return fmt.Errorf("winnotifiy: failed to initialize activated callback %w", err) + } + + // Initialize notification dismissed callback + callback = windows.NewCallback(func(id int64, actionIndex int32) uint64 { + dismissed(id, actionIndex) + return 0 + }) + rc, _, err = lib.setDismissedCallback.Call(callback) + if rc != 1 { + return fmt.Errorf("winnotifiy: failed to initialize dismissed callback %w", err) + } + + // Initialize notification failed callback + callback = windows.NewCallback(func(id int64, actionIndex int32) uint64 { + failed(id, actionIndex) + return 0 + }) + rc, _, err = lib.setFailedCallback.Call(callback) + if rc != 1 { + return fmt.Errorf("winnotifiy: failed to initialize failed callback %s", err) + } + + return nil +} + +// NewNotification starts a creation of new notification. NotificationBuilder.Delete should allays be called when done using the object or there will be memory leeks +func (lib *WinToast) NewNotification(title string, content string) (*NotificationBuilder, error) { + if lib == nil { + return nil, fmt.Errorf("wintoast: lib object was nil") + } + return newNotification(lib, title, content) +} + +// HideNotification hides notification +func (lib *WinToast) HideNotification(id int64) error { + if lib == nil { + return fmt.Errorf("wintoast: lib object was nil") + } + + lib.Lock() + defer lib.Unlock() + + rc, _, _ := lib.hideNotification.Call(uintptr(id)) + + if rc != 1 { + return fmt.Errorf("wintoast: failed to hide notification %d", id) + } + + return nil +} diff --git a/desktop/angular/assets b/desktop/angular/assets index 41aef43f..21dab851 120000 --- a/desktop/angular/assets +++ b/desktop/angular/assets @@ -1 +1 @@ -../../assets \ No newline at end of file +../../assets/data \ No newline at end of file diff --git a/go.mod b/go.mod index 6a1fc4e3..f1b496f5 100644 --- a/go.mod +++ b/go.mod @@ -8,11 +8,13 @@ toolchain go1.21.2 replace github.com/tc-hib/winres => github.com/dhaavi/winres v0.2.2 require ( + fyne.io/systray v1.10.0 github.com/Xuanwo/go-locale v1.1.0 github.com/agext/levenshtein v1.2.3 github.com/awalterschulze/gographviz v2.0.3+incompatible github.com/cilium/ebpf v0.12.3 github.com/coreos/go-iptables v0.7.0 + github.com/dhaavi/go-notify v0.0.0-20190209221809-c404b1f22435 github.com/florianl/go-conntrack v0.4.0 github.com/florianl/go-nfqueue v1.3.1 github.com/fogleman/gg v1.3.0 @@ -43,6 +45,7 @@ require ( github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26 github.com/vincent-petithory/dataurl v1.0.0 golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e + golang.org/x/image v0.15.0 golang.org/x/net v0.20.0 golang.org/x/sync v0.6.0 golang.org/x/sys v0.16.0 @@ -65,6 +68,7 @@ require ( github.com/fxamacker/cbor v1.5.1 // indirect github.com/fxamacker/cbor/v2 v2.5.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus v4.1.0+incompatible // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f // indirect @@ -106,7 +110,6 @@ require ( github.com/zeebo/blake3 v0.2.3 // indirect go.etcd.io/bbolt v1.3.8 // indirect golang.org/x/crypto v0.18.0 // indirect - golang.org/x/image v0.15.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect diff --git a/go.sum b/go.sum index e4c9c316..60ead25b 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ cloud.google.com/go v0.16.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +fyne.io/systray v1.10.0 h1:Yr1D9Lxeiw3+vSuZWPlaHC8BMjIHZXJKkek706AfYQk= +fyne.io/systray v1.10.0/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -42,6 +44,8 @@ github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dhaavi/go-notify v0.0.0-20190209221809-c404b1f22435 h1:AnwbdEI8eV3GzLM3SlrJlYmYa6OB5X8RwY4A8QJOCP0= +github.com/dhaavi/go-notify v0.0.0-20190209221809-c404b1f22435/go.mod h1:EMJ8XWTopp8OLRBMUm9vHE8Wn48CNpU21HM817OKNrc= github.com/dhaavi/winres v0.2.2 h1:SUago7FwhgLSMyDdeuV6enBZ+ZQSl0KwcnbWzvlfBls= github.com/dhaavi/winres v0.2.2/go.mod h1:1NTs+/DtKP1BplIL1+XQSoq4X1PUfLczexS7gf3x9T4= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -70,6 +74,9 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= +github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= @@ -354,6 +361,7 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/runtime/.gitkeep b/runtime/.gitkeep deleted file mode 100644 index 0d64ac96..00000000 --- a/runtime/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -The new portbase should land here. \ No newline at end of file