diff --git a/Common/CharacterEntityReference.cpp b/Common/CharacterEntityReference.cpp index 92e1b0813..e31e74ab7 100644 --- a/Common/CharacterEntityReference.cpp +++ b/Common/CharacterEntityReference.cpp @@ -1,391 +1,391 @@ -/* Copyright (C) 2015 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#include "StdAfx.h" -#include "CharacterEntityReference.h" - -namespace CharacterEntityReference { - -namespace { - -struct Entity -{ - WCHAR* name; - WCHAR ch; -}; - -// List from: -// http://www.w3.org/TR/html4/sgml/entities.html -// http://www.w3.org/TR/xhtml1/#C_16 -static Entity kEntities[] = -{ - // for markup-significant and internationalization characters - { L"quot", (WCHAR)34 }, - { L"amp", (WCHAR)38 }, - { L"apos", (WCHAR)39 }, - { L"lt", (WCHAR)60 }, - { L"gt", (WCHAR)62 }, - { L"OElig", (WCHAR)338 }, - { L"oelig", (WCHAR)339 }, - { L"Scaron", (WCHAR)352 }, - { L"scaron", (WCHAR)353 }, - { L"Yuml", (WCHAR)376 }, - { L"circ", (WCHAR)710 }, - { L"tilde", (WCHAR)732 }, - { L"ensp", (WCHAR)8194 }, - { L"emsp", (WCHAR)8195 }, - { L"thinsp", (WCHAR)8201 }, - { L"zwnj", (WCHAR)8204 }, - { L"zwj", (WCHAR)8205 }, - { L"lrm", (WCHAR)8206 }, - { L"rlm", (WCHAR)8207 }, - { L"ndash", (WCHAR)8211 }, - { L"mdash", (WCHAR)8212 }, - { L"lsquo", (WCHAR)8216 }, - { L"rsquo", (WCHAR)8217 }, - { L"sbquo", (WCHAR)8218 }, - { L"ldquo", (WCHAR)8220 }, - { L"rdquo", (WCHAR)8221 }, - { L"bdquo", (WCHAR)8222 }, - { L"dagger", (WCHAR)8224 }, - { L"Dagger", (WCHAR)8225 }, - { L"permil", (WCHAR)8240 }, - { L"lsaquo", (WCHAR)8249 }, - { L"rsaquo", (WCHAR)8250 }, - { L"euro", (WCHAR)8364 }, - - // for ISO 8859-1 characters - { L"nbsp", (WCHAR)160 }, - { L"iexcl", (WCHAR)161 }, - { L"cent", (WCHAR)162 }, - { L"pound", (WCHAR)163 }, - { L"curren", (WCHAR)164 }, - { L"yen", (WCHAR)165 }, - { L"brvbar", (WCHAR)166 }, - { L"sect", (WCHAR)167 }, - { L"uml", (WCHAR)168 }, - { L"copy", (WCHAR)169 }, - { L"ordf", (WCHAR)170 }, - { L"laquo", (WCHAR)171 }, - { L"not", (WCHAR)172 }, - { L"shy", (WCHAR)173 }, - { L"reg", (WCHAR)174 }, - { L"macr", (WCHAR)175 }, - { L"deg", (WCHAR)176 }, - { L"plusmn", (WCHAR)177 }, - { L"sup2", (WCHAR)178 }, - { L"sup3", (WCHAR)179 }, - { L"acute", (WCHAR)180 }, - { L"micro", (WCHAR)181 }, - { L"para", (WCHAR)182 }, - { L"middot", (WCHAR)183 }, - { L"cedil", (WCHAR)184 }, - { L"sup1", (WCHAR)185 }, - { L"ordm", (WCHAR)186 }, - { L"raquo", (WCHAR)187 }, - { L"frac14", (WCHAR)188 }, - { L"frac12", (WCHAR)189 }, - { L"frac34", (WCHAR)190 }, - { L"iquest", (WCHAR)191 }, - { L"Agrave", (WCHAR)192 }, - { L"Aacute", (WCHAR)193 }, - { L"Acirc", (WCHAR)194 }, - { L"Atilde", (WCHAR)195 }, - { L"Auml", (WCHAR)196 }, - { L"Aring", (WCHAR)197 }, - { L"AElig", (WCHAR)198 }, - { L"Ccedil", (WCHAR)199 }, - { L"Egrave", (WCHAR)200 }, - { L"Eacute", (WCHAR)201 }, - { L"Ecirc", (WCHAR)202 }, - { L"Euml", (WCHAR)203 }, - { L"Igrave", (WCHAR)204 }, - { L"Iacute", (WCHAR)205 }, - { L"Icirc", (WCHAR)206 }, - { L"Iuml", (WCHAR)207 }, - { L"ETH", (WCHAR)208 }, - { L"Ntilde", (WCHAR)209 }, - { L"Ograve", (WCHAR)210 }, - { L"Oacute", (WCHAR)211 }, - { L"Ocirc", (WCHAR)212 }, - { L"Otilde", (WCHAR)213 }, - { L"Ouml", (WCHAR)214 }, - { L"times", (WCHAR)215 }, - { L"Oslash", (WCHAR)216 }, - { L"Ugrave", (WCHAR)217 }, - { L"Uacute", (WCHAR)218 }, - { L"Ucirc", (WCHAR)219 }, - { L"Uuml", (WCHAR)220 }, - { L"Yacute", (WCHAR)221 }, - { L"THORN", (WCHAR)222 }, - { L"szlig", (WCHAR)223 }, - { L"agrave", (WCHAR)224 }, - { L"aacute", (WCHAR)225 }, - { L"acirc", (WCHAR)226 }, - { L"atilde", (WCHAR)227 }, - { L"auml", (WCHAR)228 }, - { L"aring", (WCHAR)229 }, - { L"aelig", (WCHAR)230 }, - { L"ccedil", (WCHAR)231 }, - { L"egrave", (WCHAR)232 }, - { L"eacute", (WCHAR)233 }, - { L"ecirc", (WCHAR)234 }, - { L"euml", (WCHAR)235 }, - { L"igrave", (WCHAR)236 }, - { L"iacute", (WCHAR)237 }, - { L"icirc", (WCHAR)238 }, - { L"iuml", (WCHAR)239 }, - { L"eth", (WCHAR)240 }, - { L"ntilde", (WCHAR)241 }, - { L"ograve", (WCHAR)242 }, - { L"oacute", (WCHAR)243 }, - { L"ocirc", (WCHAR)244 }, - { L"otilde", (WCHAR)245 }, - { L"ouml", (WCHAR)246 }, - { L"divide", (WCHAR)247 }, - { L"oslash", (WCHAR)248 }, - { L"ugrave", (WCHAR)249 }, - { L"uacute", (WCHAR)250 }, - { L"ucirc", (WCHAR)251 }, - { L"uuml", (WCHAR)252 }, - { L"yacute", (WCHAR)253 }, - { L"thorn", (WCHAR)254 }, - { L"yuml", (WCHAR)255 }, - - // for symbols, mathematical symbols, and Greek letters - { L"fnof", (WCHAR)402 }, - { L"Alpha", (WCHAR)913 }, - { L"Beta", (WCHAR)914 }, - { L"Gamma", (WCHAR)915 }, - { L"Delta", (WCHAR)916 }, - { L"Epsilon", (WCHAR)917 }, - { L"Zeta", (WCHAR)918 }, - { L"Eta", (WCHAR)919 }, - { L"Theta", (WCHAR)920 }, - { L"Iota", (WCHAR)921 }, - { L"Kappa", (WCHAR)922 }, - { L"Lambda", (WCHAR)923 }, - { L"Mu", (WCHAR)924 }, - { L"Nu", (WCHAR)925 }, - { L"Xi", (WCHAR)926 }, - { L"Omicron", (WCHAR)927 }, - { L"Pi", (WCHAR)928 }, - { L"Rho", (WCHAR)929 }, - { L"Sigma", (WCHAR)931 }, - { L"Tau", (WCHAR)932 }, - { L"Upsilon", (WCHAR)933 }, - { L"Phi", (WCHAR)934 }, - { L"Chi", (WCHAR)935 }, - { L"Psi", (WCHAR)936 }, - { L"Omega", (WCHAR)937 }, - { L"alpha", (WCHAR)945 }, - { L"beta", (WCHAR)946 }, - { L"gamma", (WCHAR)947 }, - { L"delta", (WCHAR)948 }, - { L"epsilon", (WCHAR)949 }, - { L"zeta", (WCHAR)950 }, - { L"eta", (WCHAR)951 }, - { L"theta", (WCHAR)952 }, - { L"iota", (WCHAR)953 }, - { L"kappa", (WCHAR)954 }, - { L"lambda", (WCHAR)955 }, - { L"mu", (WCHAR)956 }, - { L"nu", (WCHAR)957 }, - { L"xi", (WCHAR)958 }, - { L"omicron", (WCHAR)959 }, - { L"pi", (WCHAR)960 }, - { L"rho", (WCHAR)961 }, - { L"sigmaf", (WCHAR)962 }, - { L"sigma", (WCHAR)963 }, - { L"tau", (WCHAR)964 }, - { L"upsilon", (WCHAR)965 }, - { L"phi", (WCHAR)966 }, - { L"chi", (WCHAR)967 }, - { L"psi", (WCHAR)968 }, - { L"omega", (WCHAR)969 }, - { L"thetasym", (WCHAR)977 }, - { L"upsih", (WCHAR)978 }, - { L"piv", (WCHAR)982 }, - { L"bull", (WCHAR)8226 }, - { L"hellip", (WCHAR)8230 }, - { L"prime", (WCHAR)8242 }, - { L"Prime", (WCHAR)8243 }, - { L"oline", (WCHAR)8254 }, - { L"frasl", (WCHAR)8260 }, - { L"weierp", (WCHAR)8472 }, - { L"image", (WCHAR)8465 }, - { L"real", (WCHAR)8476 }, - { L"trade", (WCHAR)8482 }, - { L"alefsym", (WCHAR)8501 }, - { L"larr", (WCHAR)8592 }, - { L"uarr", (WCHAR)8593 }, - { L"rarr", (WCHAR)8594 }, - { L"darr", (WCHAR)8595 }, - { L"harr", (WCHAR)8596 }, - { L"crarr", (WCHAR)8629 }, - { L"lArr", (WCHAR)8656 }, - { L"uArr", (WCHAR)8657 }, - { L"rArr", (WCHAR)8658 }, - { L"dArr", (WCHAR)8659 }, - { L"hArr", (WCHAR)8660 }, - { L"forall", (WCHAR)8704 }, - { L"part", (WCHAR)8706 }, - { L"exist", (WCHAR)8707 }, - { L"empty", (WCHAR)8709 }, - { L"nabla", (WCHAR)8711 }, - { L"isin", (WCHAR)8712 }, - { L"notin", (WCHAR)8713 }, - { L"ni", (WCHAR)8715 }, - { L"prod", (WCHAR)8719 }, - { L"sum", (WCHAR)8721 }, - { L"minus", (WCHAR)8722 }, - { L"lowast", (WCHAR)8727 }, - { L"radic", (WCHAR)8730 }, - { L"prop", (WCHAR)8733 }, - { L"infin", (WCHAR)8734 }, - { L"ang", (WCHAR)8736 }, - { L"and", (WCHAR)8743 }, - { L"or", (WCHAR)8744 }, - { L"cap", (WCHAR)8745 }, - { L"cup", (WCHAR)8746 }, - { L"int", (WCHAR)8747 }, - { L"there4", (WCHAR)8756 }, - { L"sim", (WCHAR)8764 }, - { L"cong", (WCHAR)8773 }, - { L"asymp", (WCHAR)8776 }, - { L"ne", (WCHAR)8800 }, - { L"equiv", (WCHAR)8801 }, - { L"le", (WCHAR)8804 }, - { L"ge", (WCHAR)8805 }, - { L"sub", (WCHAR)8834 }, - { L"sup", (WCHAR)8835 }, - { L"nsub", (WCHAR)8836 }, - { L"sube", (WCHAR)8838 }, - { L"supe", (WCHAR)8839 }, - { L"oplus", (WCHAR)8853 }, - { L"otimes", (WCHAR)8855 }, - { L"perp", (WCHAR)8869 }, - { L"sdot", (WCHAR)8901 }, - { L"lceil", (WCHAR)8968 }, - { L"rceil", (WCHAR)8969 }, - { L"lfloor", (WCHAR)8970 }, - { L"rfloor", (WCHAR)8971 }, - { L"lang", (WCHAR)9001 }, - { L"rang", (WCHAR)9002 }, - { L"loz", (WCHAR)9674 }, - { L"spades", (WCHAR)9824 }, - { L"clubs", (WCHAR)9827 }, - { L"hearts", (WCHAR)9829 }, - { L"diams", (WCHAR)9830 } -}; - -WCHAR GetEntityChar(const std::wstring& entity) -{ - static std::unordered_map s_Map = []() { - std::unordered_map map; - const size_t entityCount = _countof(kEntities); - map.rehash(entityCount); - for (size_t i = 0; i < entityCount; ++i) - { - map.insert(std::make_pair(kEntities[i].name, kEntities[i].ch)); - } - return map; - } (); - - auto it = s_Map.find(entity); - return it != s_Map.end() ? it->second : (WCHAR)0; -} - -} // namespace - -void Decode(std::wstring& str, int opt) -{ - // (opt <= 0 || opt > 3) : Do nothing. - // (opt == 1) : Decode both numeric character references and character entity references. - // (opt == 2) : Decode only numeric character references. - // (opt == 3) : Decode only character entity references. - - if (opt >= 1 && opt <= 3) - { - std::wstring::size_type start = 0; - - while ((start = str.find(L'&', start)) != std::wstring::npos) - { - std::wstring::size_type end, pos; - - if ((end = str.find(L';', start)) == std::wstring::npos) break; - pos = start + 1; - - if (pos == end) // &; - skip - { - start = end + 1; - continue; - } - else if ((end - pos) > 10) // name (or number) is too long - { - ++start; - continue; - } - - if (str[pos] == L'#') // Numeric character reference - { - if (opt == 3 || // Decode only character entity references, - ++pos == end) // &#; - skip - { - start = end + 1; - continue; - } - - int base; - if (str[pos] == L'x' || str[pos] == L'X') - { - if (++pos == end) // &#x; or &#X; - skip - { - start = end + 1; - continue; - } - base = 16; - } - else - { - base = 10; - } - - std::wstring num(str, pos, end - pos); - WCHAR* pch = nullptr; - errno = 0; - long ch = wcstol(num.c_str(), &pch, base); - if (pch == nullptr || *pch != L'\0' || errno == ERANGE || ch <= 0 || ch >= 0xFFFE) // invalid character - { - start = pos; - continue; - } - str.replace(start, end - start + 1, 1, (WCHAR)ch); - ++start; - } - else // Character entity reference - { - if (opt == 2) // Decode only numeric character references - skip - { - start = end + 1; - continue; - } - - std::wstring name(str, pos, end - pos); - - WCHAR ch = GetEntityChar(name); - if (ch) - { - str.replace(start, end - start + 1, 1, ch); - } - ++start; - } - } - } -} - -} // namespace CharacterEntityReference +/* Copyright (C) 2015 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#include "StdAfx.h" +#include "CharacterEntityReference.h" + +namespace CharacterEntityReference { + +namespace { + +struct Entity +{ + WCHAR* name; + WCHAR ch; +}; + +// List from: +// http://www.w3.org/TR/html4/sgml/entities.html +// http://www.w3.org/TR/xhtml1/#C_16 +static Entity kEntities[] = +{ + // for markup-significant and internationalization characters + { L"quot", (WCHAR)34 }, + { L"amp", (WCHAR)38 }, + { L"apos", (WCHAR)39 }, + { L"lt", (WCHAR)60 }, + { L"gt", (WCHAR)62 }, + { L"OElig", (WCHAR)338 }, + { L"oelig", (WCHAR)339 }, + { L"Scaron", (WCHAR)352 }, + { L"scaron", (WCHAR)353 }, + { L"Yuml", (WCHAR)376 }, + { L"circ", (WCHAR)710 }, + { L"tilde", (WCHAR)732 }, + { L"ensp", (WCHAR)8194 }, + { L"emsp", (WCHAR)8195 }, + { L"thinsp", (WCHAR)8201 }, + { L"zwnj", (WCHAR)8204 }, + { L"zwj", (WCHAR)8205 }, + { L"lrm", (WCHAR)8206 }, + { L"rlm", (WCHAR)8207 }, + { L"ndash", (WCHAR)8211 }, + { L"mdash", (WCHAR)8212 }, + { L"lsquo", (WCHAR)8216 }, + { L"rsquo", (WCHAR)8217 }, + { L"sbquo", (WCHAR)8218 }, + { L"ldquo", (WCHAR)8220 }, + { L"rdquo", (WCHAR)8221 }, + { L"bdquo", (WCHAR)8222 }, + { L"dagger", (WCHAR)8224 }, + { L"Dagger", (WCHAR)8225 }, + { L"permil", (WCHAR)8240 }, + { L"lsaquo", (WCHAR)8249 }, + { L"rsaquo", (WCHAR)8250 }, + { L"euro", (WCHAR)8364 }, + + // for ISO 8859-1 characters + { L"nbsp", (WCHAR)160 }, + { L"iexcl", (WCHAR)161 }, + { L"cent", (WCHAR)162 }, + { L"pound", (WCHAR)163 }, + { L"curren", (WCHAR)164 }, + { L"yen", (WCHAR)165 }, + { L"brvbar", (WCHAR)166 }, + { L"sect", (WCHAR)167 }, + { L"uml", (WCHAR)168 }, + { L"copy", (WCHAR)169 }, + { L"ordf", (WCHAR)170 }, + { L"laquo", (WCHAR)171 }, + { L"not", (WCHAR)172 }, + { L"shy", (WCHAR)173 }, + { L"reg", (WCHAR)174 }, + { L"macr", (WCHAR)175 }, + { L"deg", (WCHAR)176 }, + { L"plusmn", (WCHAR)177 }, + { L"sup2", (WCHAR)178 }, + { L"sup3", (WCHAR)179 }, + { L"acute", (WCHAR)180 }, + { L"micro", (WCHAR)181 }, + { L"para", (WCHAR)182 }, + { L"middot", (WCHAR)183 }, + { L"cedil", (WCHAR)184 }, + { L"sup1", (WCHAR)185 }, + { L"ordm", (WCHAR)186 }, + { L"raquo", (WCHAR)187 }, + { L"frac14", (WCHAR)188 }, + { L"frac12", (WCHAR)189 }, + { L"frac34", (WCHAR)190 }, + { L"iquest", (WCHAR)191 }, + { L"Agrave", (WCHAR)192 }, + { L"Aacute", (WCHAR)193 }, + { L"Acirc", (WCHAR)194 }, + { L"Atilde", (WCHAR)195 }, + { L"Auml", (WCHAR)196 }, + { L"Aring", (WCHAR)197 }, + { L"AElig", (WCHAR)198 }, + { L"Ccedil", (WCHAR)199 }, + { L"Egrave", (WCHAR)200 }, + { L"Eacute", (WCHAR)201 }, + { L"Ecirc", (WCHAR)202 }, + { L"Euml", (WCHAR)203 }, + { L"Igrave", (WCHAR)204 }, + { L"Iacute", (WCHAR)205 }, + { L"Icirc", (WCHAR)206 }, + { L"Iuml", (WCHAR)207 }, + { L"ETH", (WCHAR)208 }, + { L"Ntilde", (WCHAR)209 }, + { L"Ograve", (WCHAR)210 }, + { L"Oacute", (WCHAR)211 }, + { L"Ocirc", (WCHAR)212 }, + { L"Otilde", (WCHAR)213 }, + { L"Ouml", (WCHAR)214 }, + { L"times", (WCHAR)215 }, + { L"Oslash", (WCHAR)216 }, + { L"Ugrave", (WCHAR)217 }, + { L"Uacute", (WCHAR)218 }, + { L"Ucirc", (WCHAR)219 }, + { L"Uuml", (WCHAR)220 }, + { L"Yacute", (WCHAR)221 }, + { L"THORN", (WCHAR)222 }, + { L"szlig", (WCHAR)223 }, + { L"agrave", (WCHAR)224 }, + { L"aacute", (WCHAR)225 }, + { L"acirc", (WCHAR)226 }, + { L"atilde", (WCHAR)227 }, + { L"auml", (WCHAR)228 }, + { L"aring", (WCHAR)229 }, + { L"aelig", (WCHAR)230 }, + { L"ccedil", (WCHAR)231 }, + { L"egrave", (WCHAR)232 }, + { L"eacute", (WCHAR)233 }, + { L"ecirc", (WCHAR)234 }, + { L"euml", (WCHAR)235 }, + { L"igrave", (WCHAR)236 }, + { L"iacute", (WCHAR)237 }, + { L"icirc", (WCHAR)238 }, + { L"iuml", (WCHAR)239 }, + { L"eth", (WCHAR)240 }, + { L"ntilde", (WCHAR)241 }, + { L"ograve", (WCHAR)242 }, + { L"oacute", (WCHAR)243 }, + { L"ocirc", (WCHAR)244 }, + { L"otilde", (WCHAR)245 }, + { L"ouml", (WCHAR)246 }, + { L"divide", (WCHAR)247 }, + { L"oslash", (WCHAR)248 }, + { L"ugrave", (WCHAR)249 }, + { L"uacute", (WCHAR)250 }, + { L"ucirc", (WCHAR)251 }, + { L"uuml", (WCHAR)252 }, + { L"yacute", (WCHAR)253 }, + { L"thorn", (WCHAR)254 }, + { L"yuml", (WCHAR)255 }, + + // for symbols, mathematical symbols, and Greek letters + { L"fnof", (WCHAR)402 }, + { L"Alpha", (WCHAR)913 }, + { L"Beta", (WCHAR)914 }, + { L"Gamma", (WCHAR)915 }, + { L"Delta", (WCHAR)916 }, + { L"Epsilon", (WCHAR)917 }, + { L"Zeta", (WCHAR)918 }, + { L"Eta", (WCHAR)919 }, + { L"Theta", (WCHAR)920 }, + { L"Iota", (WCHAR)921 }, + { L"Kappa", (WCHAR)922 }, + { L"Lambda", (WCHAR)923 }, + { L"Mu", (WCHAR)924 }, + { L"Nu", (WCHAR)925 }, + { L"Xi", (WCHAR)926 }, + { L"Omicron", (WCHAR)927 }, + { L"Pi", (WCHAR)928 }, + { L"Rho", (WCHAR)929 }, + { L"Sigma", (WCHAR)931 }, + { L"Tau", (WCHAR)932 }, + { L"Upsilon", (WCHAR)933 }, + { L"Phi", (WCHAR)934 }, + { L"Chi", (WCHAR)935 }, + { L"Psi", (WCHAR)936 }, + { L"Omega", (WCHAR)937 }, + { L"alpha", (WCHAR)945 }, + { L"beta", (WCHAR)946 }, + { L"gamma", (WCHAR)947 }, + { L"delta", (WCHAR)948 }, + { L"epsilon", (WCHAR)949 }, + { L"zeta", (WCHAR)950 }, + { L"eta", (WCHAR)951 }, + { L"theta", (WCHAR)952 }, + { L"iota", (WCHAR)953 }, + { L"kappa", (WCHAR)954 }, + { L"lambda", (WCHAR)955 }, + { L"mu", (WCHAR)956 }, + { L"nu", (WCHAR)957 }, + { L"xi", (WCHAR)958 }, + { L"omicron", (WCHAR)959 }, + { L"pi", (WCHAR)960 }, + { L"rho", (WCHAR)961 }, + { L"sigmaf", (WCHAR)962 }, + { L"sigma", (WCHAR)963 }, + { L"tau", (WCHAR)964 }, + { L"upsilon", (WCHAR)965 }, + { L"phi", (WCHAR)966 }, + { L"chi", (WCHAR)967 }, + { L"psi", (WCHAR)968 }, + { L"omega", (WCHAR)969 }, + { L"thetasym", (WCHAR)977 }, + { L"upsih", (WCHAR)978 }, + { L"piv", (WCHAR)982 }, + { L"bull", (WCHAR)8226 }, + { L"hellip", (WCHAR)8230 }, + { L"prime", (WCHAR)8242 }, + { L"Prime", (WCHAR)8243 }, + { L"oline", (WCHAR)8254 }, + { L"frasl", (WCHAR)8260 }, + { L"weierp", (WCHAR)8472 }, + { L"image", (WCHAR)8465 }, + { L"real", (WCHAR)8476 }, + { L"trade", (WCHAR)8482 }, + { L"alefsym", (WCHAR)8501 }, + { L"larr", (WCHAR)8592 }, + { L"uarr", (WCHAR)8593 }, + { L"rarr", (WCHAR)8594 }, + { L"darr", (WCHAR)8595 }, + { L"harr", (WCHAR)8596 }, + { L"crarr", (WCHAR)8629 }, + { L"lArr", (WCHAR)8656 }, + { L"uArr", (WCHAR)8657 }, + { L"rArr", (WCHAR)8658 }, + { L"dArr", (WCHAR)8659 }, + { L"hArr", (WCHAR)8660 }, + { L"forall", (WCHAR)8704 }, + { L"part", (WCHAR)8706 }, + { L"exist", (WCHAR)8707 }, + { L"empty", (WCHAR)8709 }, + { L"nabla", (WCHAR)8711 }, + { L"isin", (WCHAR)8712 }, + { L"notin", (WCHAR)8713 }, + { L"ni", (WCHAR)8715 }, + { L"prod", (WCHAR)8719 }, + { L"sum", (WCHAR)8721 }, + { L"minus", (WCHAR)8722 }, + { L"lowast", (WCHAR)8727 }, + { L"radic", (WCHAR)8730 }, + { L"prop", (WCHAR)8733 }, + { L"infin", (WCHAR)8734 }, + { L"ang", (WCHAR)8736 }, + { L"and", (WCHAR)8743 }, + { L"or", (WCHAR)8744 }, + { L"cap", (WCHAR)8745 }, + { L"cup", (WCHAR)8746 }, + { L"int", (WCHAR)8747 }, + { L"there4", (WCHAR)8756 }, + { L"sim", (WCHAR)8764 }, + { L"cong", (WCHAR)8773 }, + { L"asymp", (WCHAR)8776 }, + { L"ne", (WCHAR)8800 }, + { L"equiv", (WCHAR)8801 }, + { L"le", (WCHAR)8804 }, + { L"ge", (WCHAR)8805 }, + { L"sub", (WCHAR)8834 }, + { L"sup", (WCHAR)8835 }, + { L"nsub", (WCHAR)8836 }, + { L"sube", (WCHAR)8838 }, + { L"supe", (WCHAR)8839 }, + { L"oplus", (WCHAR)8853 }, + { L"otimes", (WCHAR)8855 }, + { L"perp", (WCHAR)8869 }, + { L"sdot", (WCHAR)8901 }, + { L"lceil", (WCHAR)8968 }, + { L"rceil", (WCHAR)8969 }, + { L"lfloor", (WCHAR)8970 }, + { L"rfloor", (WCHAR)8971 }, + { L"lang", (WCHAR)9001 }, + { L"rang", (WCHAR)9002 }, + { L"loz", (WCHAR)9674 }, + { L"spades", (WCHAR)9824 }, + { L"clubs", (WCHAR)9827 }, + { L"hearts", (WCHAR)9829 }, + { L"diams", (WCHAR)9830 } +}; + +WCHAR GetEntityChar(const std::wstring& entity) +{ + static std::unordered_map s_Map = []() { + std::unordered_map map; + const size_t entityCount = _countof(kEntities); + map.rehash(entityCount); + for (size_t i = 0; i < entityCount; ++i) + { + map.insert(std::make_pair(kEntities[i].name, kEntities[i].ch)); + } + return map; + } (); + + auto it = s_Map.find(entity); + return it != s_Map.end() ? it->second : (WCHAR)0; +} + +} // namespace + +void Decode(std::wstring& str, int opt) +{ + // (opt <= 0 || opt > 3) : Do nothing. + // (opt == 1) : Decode both numeric character references and character entity references. + // (opt == 2) : Decode only numeric character references. + // (opt == 3) : Decode only character entity references. + + if (opt >= 1 && opt <= 3) + { + std::wstring::size_type start = 0; + + while ((start = str.find(L'&', start)) != std::wstring::npos) + { + std::wstring::size_type end, pos; + + if ((end = str.find(L';', start)) == std::wstring::npos) break; + pos = start + 1; + + if (pos == end) // &; - skip + { + start = end + 1; + continue; + } + else if ((end - pos) > 10) // name (or number) is too long + { + ++start; + continue; + } + + if (str[pos] == L'#') // Numeric character reference + { + if (opt == 3 || // Decode only character entity references, + ++pos == end) // &#; - skip + { + start = end + 1; + continue; + } + + int base; + if (str[pos] == L'x' || str[pos] == L'X') + { + if (++pos == end) // &#x; or &#X; - skip + { + start = end + 1; + continue; + } + base = 16; + } + else + { + base = 10; + } + + std::wstring num(str, pos, end - pos); + WCHAR* pch = nullptr; + errno = 0; + long ch = wcstol(num.c_str(), &pch, base); + if (pch == nullptr || *pch != L'\0' || errno == ERANGE || ch <= 0 || ch >= 0xFFFE) // invalid character + { + start = pos; + continue; + } + str.replace(start, end - start + 1, 1, (WCHAR)ch); + ++start; + } + else // Character entity reference + { + if (opt == 2) // Decode only numeric character references - skip + { + start = end + 1; + continue; + } + + std::wstring name(str, pos, end - pos); + + WCHAR ch = GetEntityChar(name); + if (ch) + { + str.replace(start, end - start + 1, 1, ch); + } + ++start; + } + } + } +} + +} // namespace CharacterEntityReference diff --git a/Common/CharacterEntityReference.h b/Common/CharacterEntityReference.h index 7cd75cbdd..0fb392e9c 100644 --- a/Common/CharacterEntityReference.h +++ b/Common/CharacterEntityReference.h @@ -1,19 +1,19 @@ -/* Copyright (C) 2015 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#ifndef RM_COMMON_CHARACTERENTITYREFERENCE_H_ -#define RM_COMMON_CHARACTERENTITYREFERENCE_H_ - -#include - -namespace CharacterEntityReference { - -void Decode(std::wstring& str, int opt); - -} // namespace CharacterEntityReference - -#endif +/* Copyright (C) 2015 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#ifndef RM_COMMON_CHARACTERENTITYREFERENCE_H_ +#define RM_COMMON_CHARACTERENTITYREFERENCE_H_ + +#include + +namespace CharacterEntityReference { + +void Decode(std::wstring& str, int opt); + +} // namespace CharacterEntityReference + +#endif diff --git a/Common/Gfx/Canvas.h b/Common/Gfx/Canvas.h index 2fb6bbd17..3318b4dff 100644 --- a/Common/Gfx/Canvas.h +++ b/Common/Gfx/Canvas.h @@ -1,167 +1,167 @@ -/* Copyright (C) 2013 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#ifndef RM_GFX_CANVAS_H_ -#define RM_GFX_CANVAS_H_ - -#include "FontCollectionD2D.h" -#include "Shape.h" -#include "TextFormatD2D.h" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Gfx { - -// Forward declaration -class D2DBitmap; - -class RenderTexture; - -namespace Util { - class D2DBitmapLoader; - class D2DEffectStream; -} - -// Wraps Direct2D/DirectWrite. -class Canvas -{ -public: - Canvas(); - ~Canvas(); - - static bool Initialize(bool hardwareAccelerated); - static void Finalize(); - - bool InitializeRenderTarget(HWND hwnd); - - int GetW() const { return m_W; } - int GetH() const { return m_H; } - - void SetAccurateText(bool option) { m_AccurateText = option; } - - // Resize the draw area of the Canvas. This function must not be called if BeginDraw() has been - // called and has not yet been matched by a correspoding call to EndDraw. - void Resize(int w, int h); - - bool BeginDraw(); - void EndDraw(); - - HDC GetDC(); - void ReleaseDC(); - - FontCollection* CreateFontCollection() { return new FontCollectionD2D(); } - TextFormat* CreateTextFormat() { return new TextFormatD2D(); } - - bool IsTransparentPixel(int x, int y); - - bool IsDrawing() { return m_IsDrawing; } - - void GetTransform(D2D1_MATRIX_3X2_F* matrix); - void SetTransform(const D2D1_MATRIX_3X2_F& matrix); - void ResetTransform(); - - void PushClip(Gfx::Shape* clip); - void PopClip(); - - bool SetTarget(Gfx::RenderTexture* texture); - void ResetTarget(); - - void SetAntiAliasing(bool enable); - void SetTextAntiAliasing(bool enable); - - void Clear(const D2D1_COLOR_F& color = Util::c_Transparent_Color_F); - - void DrawTextW(const std::wstring& srcStr, const TextFormat& format, const D2D1_RECT_F& rect, - const D2D1_COLOR_F& color, bool applyInlineFormatting = false); - bool MeasureTextW(const std::wstring& srcStr, const TextFormat& format, D2D1_SIZE_F& size); - bool MeasureTextLinesW(const std::wstring& srcStr, const TextFormat& format, D2D1_SIZE_F& size, UINT32& lines); - - void DrawBitmap(const D2DBitmap* bitmap, const D2D1_RECT_F& dstRect, const D2D1_RECT_F& srcRect); - void DrawTiledBitmap(const D2DBitmap* bitmap, const D2D1_RECT_F& dstRect, const D2D1_RECT_F& srcRect); - void DrawMaskedBitmap(const D2DBitmap* bitmap, const D2DBitmap* maskBitmap, const D2D1_RECT_F& dstRect, - const D2D1_RECT_F& srcRect, const D2D1_RECT_F& srcRect2); - - void FillRectangle(const D2D1_RECT_F& rect, const D2D1_COLOR_F& color); - void FillGradientRectangle(const D2D1_RECT_F& rect, const D2D1_COLOR_F& color1, const D2D1_COLOR_F& color2, const FLOAT& angle); - - void DrawLine(const D2D1_COLOR_F& color, FLOAT x1, FLOAT y1, FLOAT x2, FLOAT y2, FLOAT strokeWidth = 1.0f); - - void DrawGeometry(Shape& shape, int x, int y); - -private: - friend class Canvas; - friend class D2DBitmap; - friend class RenderTexture; - friend class FontCollectionD2D; - friend class TextFormatD2D; - friend class TextInlineFormat_Face; - friend class TextInlineFormat_Typography; - friend class Shape; - friend class Rectangle; - friend class RoundedRectangle; - friend class Ellipse; - friend class Line; - friend class Arc; - friend class Curve; - friend class QuadraticCurve; - friend class Path; - friend class Util::D2DBitmapLoader; - friend class Util::D2DEffectStream; - - Canvas(const Canvas& other) = delete; - Canvas& operator=(Canvas other) = delete; - - HRESULT CreateRenderTarget(); - bool CreateTargetBitmap(UINT32 width, UINT32 height); - - Microsoft::WRL::ComPtr m_Target; - Microsoft::WRL::ComPtr m_SwapChain; - Microsoft::WRL::ComPtr m_BackBuffer; - Microsoft::WRL::ComPtr m_TargetBitmap; - - std::stack> m_Layers; - - int m_W; - int m_H; - UINT32 m_MaxBitmapSize; - - bool m_IsDrawing; - bool m_EnableDrawAfterGdi; - - // GDI+, by default, includes padding around the string and also has a larger character spacing - // compared to DirectWrite. In order to minimize diffeences between the text renderers, - // an option is provided to enable accurate (typographic) text rendering. If set to |true|, - // it is expected that there is no padding around the text and that the output is similar to - // the default DirectWrite output. Otherwise, the expected result should be similar to that of - // non-typographic GDI+. - bool m_AccurateText; - - bool m_TextAntiAliasing; - - // |true| if PushAxisAlignedClip()/PopAxisAlignedClip() can be used. - bool m_CanUseAxisAlignClip; - - static UINT c_Instances; - static D3D_FEATURE_LEVEL c_FeatureLevel; - static Microsoft::WRL::ComPtr c_D3DDevice; - static Microsoft::WRL::ComPtr c_D3DContext; - static Microsoft::WRL::ComPtr c_D2DDevice; - static Microsoft::WRL::ComPtr c_DxgiDevice; - static Microsoft::WRL::ComPtr c_D2DFactory; - static Microsoft::WRL::ComPtr c_DWFactory; - static Microsoft::WRL::ComPtr c_WICFactory; -}; - -} // namespace Gfx - -#endif +/* Copyright (C) 2013 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#ifndef RM_GFX_CANVAS_H_ +#define RM_GFX_CANVAS_H_ + +#include "FontCollectionD2D.h" +#include "Shape.h" +#include "TextFormatD2D.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gfx { + +// Forward declaration +class D2DBitmap; + +class RenderTexture; + +namespace Util { + class D2DBitmapLoader; + class D2DEffectStream; +} + +// Wraps Direct2D/DirectWrite. +class Canvas +{ +public: + Canvas(); + ~Canvas(); + + static bool Initialize(bool hardwareAccelerated); + static void Finalize(); + + bool InitializeRenderTarget(HWND hwnd); + + int GetW() const { return m_W; } + int GetH() const { return m_H; } + + void SetAccurateText(bool option) { m_AccurateText = option; } + + // Resize the draw area of the Canvas. This function must not be called if BeginDraw() has been + // called and has not yet been matched by a correspoding call to EndDraw. + void Resize(int w, int h); + + bool BeginDraw(); + void EndDraw(); + + HDC GetDC(); + void ReleaseDC(); + + FontCollection* CreateFontCollection() { return new FontCollectionD2D(); } + TextFormat* CreateTextFormat() { return new TextFormatD2D(); } + + bool IsTransparentPixel(int x, int y); + + bool IsDrawing() { return m_IsDrawing; } + + void GetTransform(D2D1_MATRIX_3X2_F* matrix); + void SetTransform(const D2D1_MATRIX_3X2_F& matrix); + void ResetTransform(); + + void PushClip(Gfx::Shape* clip); + void PopClip(); + + bool SetTarget(Gfx::RenderTexture* texture); + void ResetTarget(); + + void SetAntiAliasing(bool enable); + void SetTextAntiAliasing(bool enable); + + void Clear(const D2D1_COLOR_F& color = Util::c_Transparent_Color_F); + + void DrawTextW(const std::wstring& srcStr, const TextFormat& format, const D2D1_RECT_F& rect, + const D2D1_COLOR_F& color, bool applyInlineFormatting = false); + bool MeasureTextW(const std::wstring& srcStr, const TextFormat& format, D2D1_SIZE_F& size); + bool MeasureTextLinesW(const std::wstring& srcStr, const TextFormat& format, D2D1_SIZE_F& size, UINT32& lines); + + void DrawBitmap(const D2DBitmap* bitmap, const D2D1_RECT_F& dstRect, const D2D1_RECT_F& srcRect); + void DrawTiledBitmap(const D2DBitmap* bitmap, const D2D1_RECT_F& dstRect, const D2D1_RECT_F& srcRect); + void DrawMaskedBitmap(const D2DBitmap* bitmap, const D2DBitmap* maskBitmap, const D2D1_RECT_F& dstRect, + const D2D1_RECT_F& srcRect, const D2D1_RECT_F& srcRect2); + + void FillRectangle(const D2D1_RECT_F& rect, const D2D1_COLOR_F& color); + void FillGradientRectangle(const D2D1_RECT_F& rect, const D2D1_COLOR_F& color1, const D2D1_COLOR_F& color2, const FLOAT& angle); + + void DrawLine(const D2D1_COLOR_F& color, FLOAT x1, FLOAT y1, FLOAT x2, FLOAT y2, FLOAT strokeWidth = 1.0f); + + void DrawGeometry(Shape& shape, int x, int y); + +private: + friend class Canvas; + friend class D2DBitmap; + friend class RenderTexture; + friend class FontCollectionD2D; + friend class TextFormatD2D; + friend class TextInlineFormat_Face; + friend class TextInlineFormat_Typography; + friend class Shape; + friend class Rectangle; + friend class RoundedRectangle; + friend class Ellipse; + friend class Line; + friend class Arc; + friend class Curve; + friend class QuadraticCurve; + friend class Path; + friend class Util::D2DBitmapLoader; + friend class Util::D2DEffectStream; + + Canvas(const Canvas& other) = delete; + Canvas& operator=(Canvas other) = delete; + + HRESULT CreateRenderTarget(); + bool CreateTargetBitmap(UINT32 width, UINT32 height); + + Microsoft::WRL::ComPtr m_Target; + Microsoft::WRL::ComPtr m_SwapChain; + Microsoft::WRL::ComPtr m_BackBuffer; + Microsoft::WRL::ComPtr m_TargetBitmap; + + std::stack> m_Layers; + + int m_W; + int m_H; + UINT32 m_MaxBitmapSize; + + bool m_IsDrawing; + bool m_EnableDrawAfterGdi; + + // GDI+, by default, includes padding around the string and also has a larger character spacing + // compared to DirectWrite. In order to minimize diffeences between the text renderers, + // an option is provided to enable accurate (typographic) text rendering. If set to |true|, + // it is expected that there is no padding around the text and that the output is similar to + // the default DirectWrite output. Otherwise, the expected result should be similar to that of + // non-typographic GDI+. + bool m_AccurateText; + + bool m_TextAntiAliasing; + + // |true| if PushAxisAlignedClip()/PopAxisAlignedClip() can be used. + bool m_CanUseAxisAlignClip; + + static UINT c_Instances; + static D3D_FEATURE_LEVEL c_FeatureLevel; + static Microsoft::WRL::ComPtr c_D3DDevice; + static Microsoft::WRL::ComPtr c_D3DContext; + static Microsoft::WRL::ComPtr c_D2DDevice; + static Microsoft::WRL::ComPtr c_DxgiDevice; + static Microsoft::WRL::ComPtr c_D2DFactory; + static Microsoft::WRL::ComPtr c_DWFactory; + static Microsoft::WRL::ComPtr c_WICFactory; +}; + +} // namespace Gfx + +#endif diff --git a/Common/Gfx/D2DBitmap.cpp b/Common/Gfx/D2DBitmap.cpp index 09746ac34..75c272103 100644 --- a/Common/Gfx/D2DBitmap.cpp +++ b/Common/Gfx/D2DBitmap.cpp @@ -1,153 +1,153 @@ -/* Copyright (C) 2018 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#include "StdAfx.h" -#include "D2DBitmap.h" -#include "Util/D2DBitmapLoader.h" -#include "Util/D2DEffectStream.h" - -namespace Gfx { - -BitmapSegment::BitmapSegment(Microsoft::WRL::ComPtr& bitmap, - UINT x, UINT y, UINT width, UINT height) : - m_Bitmap(std::move(bitmap)), - m_X(x), - m_Y(y), - m_Width(width), - m_Height(height) -{ -} - -BitmapSegment::BitmapSegment(Microsoft::WRL::ComPtr& bitmap, D2D1_RECT_U& rect) : - m_Bitmap(std::move(bitmap)), - m_X(rect.left), - m_Y(rect.top), - m_Width(rect.right), - m_Height(rect.bottom) -{ -} - -BitmapSegment::BitmapSegment(Microsoft::WRL::ComPtr& bitmap, WICRect& rect) : - m_Bitmap(std::move(bitmap)), - m_X(rect.X), - m_Y(rect.Y), - m_Width(rect.Width), - m_Height(rect.Height) -{ -} - -D2DBitmap::D2DBitmap(const std::wstring& path, int exifOrientation) : - m_Width(0U), - m_Height(0U), - m_ExifOrientation(exifOrientation), - m_Path(path), - m_FileSize(0UL), - m_FileTime(0ULL) - -{ -} - -D2DBitmap::D2DBitmap() : - m_Width(0U), - m_Height(0U), - m_ExifOrientation(0), - m_Path(L""), - m_FileSize(0UL), - m_FileTime(0ULL) -{ -} - -D2DBitmap::~D2DBitmap() -{ -} - -void D2DBitmap::AddSegment(Microsoft::WRL::ComPtr& bitmap, UINT x, UINT y, UINT width, UINT height) -{ - m_Segments.emplace_back(bitmap, x, y, width, height); -} - -void D2DBitmap::AddSegment(Microsoft::WRL::ComPtr& bitmap, D2D1_RECT_U& rect) -{ - m_Segments.emplace_back(bitmap, rect); -} - -void D2DBitmap::AddSegment(Microsoft::WRL::ComPtr& bitmap, WICRect& rect) -{ - m_Segments.emplace_back(bitmap, rect); -} - -bool D2DBitmap::HasFileChanged(const std::wstring& file) -{ - return Util::D2DBitmapLoader::HasFileChanged(this, file); -} - -HRESULT D2DBitmap::Load(const Canvas& canvas) -{ - return Util::D2DBitmapLoader::LoadBitmapFromFile(canvas, this); -} - -Util::D2DEffectStream* D2DBitmap::CreateEffectStream() -{ - return new Util::D2DEffectStream(this); -} - -bool D2DBitmap::GetPixel(Canvas& canvas, int px, int py, D2D1_COLOR_F& color) -{ - // TODO: Create a duplicate bitmap for every one with CPU_READ instead of creating a small 1 px bitmap? - // Maybe have a 1px bitmap in Canvas since we won't ever check two different bitmaps at once and use that to fetch the pixel data? - // Creating a bitmap every time we have to check a pixel is bad though, so we should consider the above. - Microsoft::WRL::ComPtr bitmap; - D2D1_BITMAP_PROPERTIES1 bProps = D2D1::BitmapProperties1( - D2D1_BITMAP_OPTIONS_CPU_READ | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, - D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)); - HRESULT hr = canvas.m_Target->CreateBitmap( - D2D1::SizeU(1U, 1U), - nullptr, - 0U, - bProps, - bitmap.ReleaseAndGetAddressOf()); - if (FAILED(hr)) return false; - - // Verify that the pixel was actually set - bool found = false; - for (auto& it : m_Segments) - { - const auto rect = it.GetRect(); - if (rect.left < px && rect.top < py && px <= rect.left + rect.right && py <= rect.top + rect.bottom) - { - const auto point = D2D1::Point2U(0U, 0U); - const auto srcRect = D2D1::RectU( - (UINT32)(px - rect.left), - (UINT32)(py - rect.top), - (UINT32)(px - rect.left + 1), - (UINT32)(py - rect.top + 1)); - bitmap->CopyFromBitmap(&point, it.GetBitmap(), &srcRect); - found = true; - break; - } - } - - if (!found) return false; - - D2D1_MAPPED_RECT data = { 0 }; - hr = bitmap->Map(D2D1_MAP_OPTIONS_READ, &data); - if (FAILED(hr)) return false; - - color.r = data.bits[0]; - color.g = data.bits[1]; - color.b = data.bits[2]; - color.a = data.bits[3]; - - hr = bitmap->Unmap(); - return SUCCEEDED(hr); -} - -HRESULT D2DBitmap::GetFileInfo(const std::wstring& path, FileInfo* fileInfo) -{ - return Util::D2DBitmapLoader::GetFileInfo(path, fileInfo); -} -} // namespace Gfx +/* Copyright (C) 2018 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#include "StdAfx.h" +#include "D2DBitmap.h" +#include "Util/D2DBitmapLoader.h" +#include "Util/D2DEffectStream.h" + +namespace Gfx { + +BitmapSegment::BitmapSegment(Microsoft::WRL::ComPtr& bitmap, + UINT x, UINT y, UINT width, UINT height) : + m_Bitmap(std::move(bitmap)), + m_X(x), + m_Y(y), + m_Width(width), + m_Height(height) +{ +} + +BitmapSegment::BitmapSegment(Microsoft::WRL::ComPtr& bitmap, D2D1_RECT_U& rect) : + m_Bitmap(std::move(bitmap)), + m_X(rect.left), + m_Y(rect.top), + m_Width(rect.right), + m_Height(rect.bottom) +{ +} + +BitmapSegment::BitmapSegment(Microsoft::WRL::ComPtr& bitmap, WICRect& rect) : + m_Bitmap(std::move(bitmap)), + m_X(rect.X), + m_Y(rect.Y), + m_Width(rect.Width), + m_Height(rect.Height) +{ +} + +D2DBitmap::D2DBitmap(const std::wstring& path, int exifOrientation) : + m_Width(0U), + m_Height(0U), + m_ExifOrientation(exifOrientation), + m_Path(path), + m_FileSize(0UL), + m_FileTime(0ULL) + +{ +} + +D2DBitmap::D2DBitmap() : + m_Width(0U), + m_Height(0U), + m_ExifOrientation(0), + m_Path(L""), + m_FileSize(0UL), + m_FileTime(0ULL) +{ +} + +D2DBitmap::~D2DBitmap() +{ +} + +void D2DBitmap::AddSegment(Microsoft::WRL::ComPtr& bitmap, UINT x, UINT y, UINT width, UINT height) +{ + m_Segments.emplace_back(bitmap, x, y, width, height); +} + +void D2DBitmap::AddSegment(Microsoft::WRL::ComPtr& bitmap, D2D1_RECT_U& rect) +{ + m_Segments.emplace_back(bitmap, rect); +} + +void D2DBitmap::AddSegment(Microsoft::WRL::ComPtr& bitmap, WICRect& rect) +{ + m_Segments.emplace_back(bitmap, rect); +} + +bool D2DBitmap::HasFileChanged(const std::wstring& file) +{ + return Util::D2DBitmapLoader::HasFileChanged(this, file); +} + +HRESULT D2DBitmap::Load(const Canvas& canvas) +{ + return Util::D2DBitmapLoader::LoadBitmapFromFile(canvas, this); +} + +Util::D2DEffectStream* D2DBitmap::CreateEffectStream() +{ + return new Util::D2DEffectStream(this); +} + +bool D2DBitmap::GetPixel(Canvas& canvas, int px, int py, D2D1_COLOR_F& color) +{ + // TODO: Create a duplicate bitmap for every one with CPU_READ instead of creating a small 1 px bitmap? + // Maybe have a 1px bitmap in Canvas since we won't ever check two different bitmaps at once and use that to fetch the pixel data? + // Creating a bitmap every time we have to check a pixel is bad though, so we should consider the above. + Microsoft::WRL::ComPtr bitmap; + D2D1_BITMAP_PROPERTIES1 bProps = D2D1::BitmapProperties1( + D2D1_BITMAP_OPTIONS_CPU_READ | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)); + HRESULT hr = canvas.m_Target->CreateBitmap( + D2D1::SizeU(1U, 1U), + nullptr, + 0U, + bProps, + bitmap.ReleaseAndGetAddressOf()); + if (FAILED(hr)) return false; + + // Verify that the pixel was actually set + bool found = false; + for (auto& it : m_Segments) + { + const auto rect = it.GetRect(); + if (rect.left < px && rect.top < py && px <= rect.left + rect.right && py <= rect.top + rect.bottom) + { + const auto point = D2D1::Point2U(0U, 0U); + const auto srcRect = D2D1::RectU( + (UINT32)(px - rect.left), + (UINT32)(py - rect.top), + (UINT32)(px - rect.left + 1), + (UINT32)(py - rect.top + 1)); + bitmap->CopyFromBitmap(&point, it.GetBitmap(), &srcRect); + found = true; + break; + } + } + + if (!found) return false; + + D2D1_MAPPED_RECT data = { 0 }; + hr = bitmap->Map(D2D1_MAP_OPTIONS_READ, &data); + if (FAILED(hr)) return false; + + color.r = data.bits[0]; + color.g = data.bits[1]; + color.b = data.bits[2]; + color.a = data.bits[3]; + + hr = bitmap->Unmap(); + return SUCCEEDED(hr); +} + +HRESULT D2DBitmap::GetFileInfo(const std::wstring& path, FileInfo* fileInfo) +{ + return Util::D2DBitmapLoader::GetFileInfo(path, fileInfo); +} +} // namespace Gfx diff --git a/Common/Gfx/D2DBitmap.h b/Common/Gfx/D2DBitmap.h index 4a17412e7..ea3443125 100644 --- a/Common/Gfx/D2DBitmap.h +++ b/Common/Gfx/D2DBitmap.h @@ -1,113 +1,113 @@ -/* Copyright (C) 2018 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#ifndef RM_GFX_UTIL_D2DBITMAP_H_ -#define RM_GFX_UTIL_D2DBITMAP_H_ - -#include "Canvas.h" - -namespace Gfx { - -class Canvas; - -class BitmapSegment -{ -public: - BitmapSegment(Microsoft::WRL::ComPtr& bitmap, UINT x, UINT y, UINT width, UINT height); - BitmapSegment(Microsoft::WRL::ComPtr& bitmap, D2D1_RECT_U& rect); - BitmapSegment(Microsoft::WRL::ComPtr& bitmap, WICRect& rect); - ~BitmapSegment() { } - - UINT GetX() { return m_X; } - UINT GetY() { return m_Y; } - - D2D1_RECT_F GetRect() { return D2D1::RectF((FLOAT)m_X, (FLOAT)m_Y, (FLOAT)m_Width, (FLOAT)m_Height); } - - ID2D1Bitmap1* GetBitmap() { return m_Bitmap.Get(); } - -private: - BitmapSegment() = delete; - - UINT m_X; - UINT m_Y; - UINT m_Width; - UINT m_Height; - - Microsoft::WRL::ComPtr m_Bitmap; -}; - -struct FileInfo -{ - FileInfo() : m_Path(), m_FileSize(), m_FileTime() - {} - - std::wstring m_Path; - DWORD m_FileSize; - ULONGLONG m_FileTime; - - bool isValid() { return !m_Path.empty() && m_FileSize != 0UL && m_FileTime != 0ULL; } -}; - -class D2DBitmap -{ -public: - D2DBitmap(const std::wstring& path, int exifOrientation = 0); - ~D2DBitmap(); - - UINT GetWidth() const{ return m_Width; } - UINT GetHeight() const{ return m_Height; } - - void AddSegment(Microsoft::WRL::ComPtr& bitmap, UINT x, UINT y, UINT width, UINT height); - void AddSegment(Microsoft::WRL::ComPtr& bitmap, D2D1_RECT_U& rect); - void AddSegment(Microsoft::WRL::ComPtr& bitmap, WICRect& rect); - - void SetSize(UINT width, UINT height) { m_Width = width; m_Height = height; } - - int GetOrientation() { return m_ExifOrientation; } - void SetOrientation(const int orientation) { m_ExifOrientation = orientation; } - - DWORD GetFileSize() { return m_FileSize; } - void SetFileSize(const DWORD& fileSize) { m_FileSize = fileSize; } - - ULONGLONG GetFileTime() { return m_FileTime; } - void SetFileTime(const ULONGLONG& fileTime) { m_FileTime= fileTime; } - - bool HasFileChanged(const std::wstring& file); - - std::wstring& GetPath() { return m_Path; } - - HRESULT Load(const Canvas& canvas); - - Util::D2DEffectStream* CreateEffectStream(); - bool GetPixel(Canvas& canvas, int px, int py, D2D1_COLOR_F& color); - - static HRESULT GetFileInfo(const std::wstring& path, FileInfo* fileInfo); - -private: - friend class Canvas; - friend class Util::D2DEffectStream; - friend class Gfx::RenderTexture; - - D2DBitmap(); - D2DBitmap(const D2DBitmap& other) = delete; - D2DBitmap& operator=(D2DBitmap other) = delete; - - UINT m_Width; - UINT m_Height; - - int m_ExifOrientation; - - std::wstring m_Path; - DWORD m_FileSize; - ULONGLONG m_FileTime; - - std::vector m_Segments; -}; - -} // namespace Gfx - -#endif +/* Copyright (C) 2018 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#ifndef RM_GFX_UTIL_D2DBITMAP_H_ +#define RM_GFX_UTIL_D2DBITMAP_H_ + +#include "Canvas.h" + +namespace Gfx { + +class Canvas; + +class BitmapSegment +{ +public: + BitmapSegment(Microsoft::WRL::ComPtr& bitmap, UINT x, UINT y, UINT width, UINT height); + BitmapSegment(Microsoft::WRL::ComPtr& bitmap, D2D1_RECT_U& rect); + BitmapSegment(Microsoft::WRL::ComPtr& bitmap, WICRect& rect); + ~BitmapSegment() { } + + UINT GetX() { return m_X; } + UINT GetY() { return m_Y; } + + D2D1_RECT_F GetRect() { return D2D1::RectF((FLOAT)m_X, (FLOAT)m_Y, (FLOAT)m_Width, (FLOAT)m_Height); } + + ID2D1Bitmap1* GetBitmap() { return m_Bitmap.Get(); } + +private: + BitmapSegment() = delete; + + UINT m_X; + UINT m_Y; + UINT m_Width; + UINT m_Height; + + Microsoft::WRL::ComPtr m_Bitmap; +}; + +struct FileInfo +{ + FileInfo() : m_Path(), m_FileSize(), m_FileTime() + {} + + std::wstring m_Path; + DWORD m_FileSize; + ULONGLONG m_FileTime; + + bool isValid() { return !m_Path.empty() && m_FileSize != 0UL && m_FileTime != 0ULL; } +}; + +class D2DBitmap +{ +public: + D2DBitmap(const std::wstring& path, int exifOrientation = 0); + ~D2DBitmap(); + + UINT GetWidth() const{ return m_Width; } + UINT GetHeight() const{ return m_Height; } + + void AddSegment(Microsoft::WRL::ComPtr& bitmap, UINT x, UINT y, UINT width, UINT height); + void AddSegment(Microsoft::WRL::ComPtr& bitmap, D2D1_RECT_U& rect); + void AddSegment(Microsoft::WRL::ComPtr& bitmap, WICRect& rect); + + void SetSize(UINT width, UINT height) { m_Width = width; m_Height = height; } + + int GetOrientation() { return m_ExifOrientation; } + void SetOrientation(const int orientation) { m_ExifOrientation = orientation; } + + DWORD GetFileSize() { return m_FileSize; } + void SetFileSize(const DWORD& fileSize) { m_FileSize = fileSize; } + + ULONGLONG GetFileTime() { return m_FileTime; } + void SetFileTime(const ULONGLONG& fileTime) { m_FileTime= fileTime; } + + bool HasFileChanged(const std::wstring& file); + + std::wstring& GetPath() { return m_Path; } + + HRESULT Load(const Canvas& canvas); + + Util::D2DEffectStream* CreateEffectStream(); + bool GetPixel(Canvas& canvas, int px, int py, D2D1_COLOR_F& color); + + static HRESULT GetFileInfo(const std::wstring& path, FileInfo* fileInfo); + +private: + friend class Canvas; + friend class Util::D2DEffectStream; + friend class Gfx::RenderTexture; + + D2DBitmap(); + D2DBitmap(const D2DBitmap& other) = delete; + D2DBitmap& operator=(D2DBitmap other) = delete; + + UINT m_Width; + UINT m_Height; + + int m_ExifOrientation; + + std::wstring m_Path; + DWORD m_FileSize; + ULONGLONG m_FileTime; + + std::vector m_Segments; +}; + +} // namespace Gfx + +#endif diff --git a/Common/Gfx/Util/D2DEffectStream.cpp b/Common/Gfx/Util/D2DEffectStream.cpp index 27416234e..43468e2fd 100644 --- a/Common/Gfx/Util/D2DEffectStream.cpp +++ b/Common/Gfx/Util/D2DEffectStream.cpp @@ -1,307 +1,307 @@ -/* Copyright (C) 2018 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#include "StdAfx.h" -#include "D2DEffectStream.h" - -namespace Gfx { -namespace Util { - -const FLOAT PI = 3.14159265f; -constexpr FLOAT ToRadians(FLOAT deg) { return deg * (PI / 180.0f); } - -void D2DEffectStream::Crop(const Canvas& canvas, const D2D1_RECT_F& crop) -{ - AddEffect(canvas, CLSID_D2D1Crop); - for (auto& effect : m_Effects) - { - effect->SetValue(D2D1_CROP_PROP_RECT, crop); - effect->SetValue(D2D1_CROP_PROP_BORDER_MODE, D2D1_BORDER_MODE_SOFT); - } - - AddEffect(canvas, CLSID_D2D12DAffineTransform); - for (auto& effect : m_Effects) - { - effect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX, D2D1::Matrix3x2F::Translation(-crop.left, -crop.top)); - } -} - -void D2DEffectStream::Tint(const Canvas& canvas, const D2D1_MATRIX_5X4_F& matrix) -{ - AddEffect(canvas, CLSID_D2D1ColorMatrix); - for (auto& effect : m_Effects) - { - effect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, matrix); - } -} - -void D2DEffectStream::Rotate(const Canvas& canvas, const FLOAT& angle) -{ - AddEffect(canvas, CLSID_D2D12DAffineTransform); - const auto size = GetSize(canvas); - const FLOAT originalW = size.width; - const FLOAT originalH = size.height; - if (originalW == 0.0f || originalH == 0.0f) return; - - const D2D1_POINT_2F pt = D2D1::Point2F(originalW / 2.0f, originalH / 2.0f); - - for (auto& effect : m_Effects) - { - const FLOAT cos_f = cos(ToRadians(angle)); - const FLOAT sin_f = sin(ToRadians(angle)); - - const FLOAT transformW = fabs(originalW * cos_f) + fabs(originalH * sin_f); - const FLOAT transformH = fabs(originalW * sin_f) + fabs(originalH * cos_f); - - const FLOAT cx = transformW / 2.0f; - const FLOAT cy = transformH / 2.0f; - - effect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX, - D2D1::Matrix3x2F::Rotation(angle, pt) * - D2D1::Matrix3x2F::Translation(cx - pt.x, cy - pt.y)); - - if (fmod(angle, 90.0f) == 0.0f) - { - effect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_BORDER_MODE, D2D1_BORDER_MODE_HARD); - } - } -} - -void D2DEffectStream::Flip(const Canvas& canvas, const FlipType& flipType) -{ - AddEffect(canvas, CLSID_D2D12DAffineTransform); - const auto size = GetSize(canvas); - if (size.width == 0.0f || size.height == 0.0f) return; - - const D2D1_POINT_2F pt = D2D1::Point2F(size.width / 2.0f, size.height / 2.0f); - - for (auto& effect : m_Effects) - { - D2D1_MATRIX_3X2_F transform = D2D1::Matrix3x2F::Identity(); - - switch (flipType) - { - case FlipType::Vertical: transform = D2D1::Matrix3x2F::Scale(1.0f, -1.0f, pt); break; - case FlipType::Horizontal: transform = D2D1::Matrix3x2F::Scale(-1.0f, 1.0f, pt); break; - case FlipType::Both: transform = D2D1::Matrix3x2F::Scale(-1.0f, -1.0f, pt); break; - - case FlipType::None: - default: - continue; - } - - effect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX, transform); - } -} - -void D2DEffectStream::ApplyExifOrientation(const Canvas& canvas) -{ - switch (m_BaseImage->GetOrientation()) - { - case 2: Flip(canvas, FlipType::Horizontal); break; - case 3: Rotate(canvas, 180.0f); break; - case 4: Flip(canvas, FlipType::Vertical); break; - case 5: Flip(canvas, FlipType::Horizontal); Rotate(canvas, 270.0f); break; - case 6: Rotate(canvas, 90.0f); break; - case 7: Flip(canvas, FlipType::Horizontal); Rotate(canvas, 90.0f); break; - case 8: Rotate(canvas, 270.0f); break; - } -} - -D2DBitmap* D2DEffectStream::ToBitmap(Canvas& canvas, const D2D1_SIZE_F* imageSize) -{ - bool changed = false; - for (const auto& effect : m_Effects) - { - changed |= !!effect; - } - if (!changed) return m_BaseImage; - - const D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET, - D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)); - - Microsoft::WRL::ComPtr target; - canvas.m_Target->GetTarget(target.GetAddressOf()); - - D2D1_MATRIX_3X2_F transform = D2D1::Matrix3x2F::Identity(); - canvas.m_Target->GetTransform(&transform); - - const UINT maxBitmapSize = (UINT)canvas.m_MaxBitmapSize; - const auto size = (imageSize) ? *imageSize : GetSize(canvas); - if (size.width < 0.0f || size.height < 0.0f) return nullptr; - - const UINT width = (UINT)size.width; - const UINT height = (UINT)size.height; - - D2DBitmap* d2dbitmap = new D2DBitmap(m_BaseImage->m_Path, m_BaseImage->m_ExifOrientation); - d2dbitmap->m_Width = width; - d2dbitmap->m_Height = height; - - auto deleteImage = [&d2dbitmap]() -> void - { - delete d2dbitmap; - d2dbitmap = nullptr; - }; - - auto didDraw = canvas.IsDrawing(); - if (didDraw) - { - canvas.m_Target->Flush(); - } - else - { - canvas.BeginDraw(); - } - - for (UINT y = 0U, H = height / maxBitmapSize; y <= H; ++y) - { - for (UINT x = 0U, W = width / maxBitmapSize; x <= W; ++x) - { - D2D1_RECT_U rect = D2D1::RectU( - (x * maxBitmapSize), - (y * maxBitmapSize), - (x == W ? (width - maxBitmapSize * x) : maxBitmapSize), // If last x coordinate, find cutoff - (y == H ? (height - maxBitmapSize * y) : maxBitmapSize)); // If last y coordinate, find cutoff - - Microsoft::WRL::ComPtr bitmap; - HRESULT hr = canvas.m_Target->CreateBitmap( - D2D1::SizeU(rect.right, rect.bottom), - nullptr, - 0U, - props, - bitmap.GetAddressOf()); - if (FAILED(hr)) - { - canvas.EndDraw(); - canvas.m_Target->SetTarget(target.Get()); - canvas.m_Target->SetTransform(transform); - deleteImage(); - return nullptr; - } - - canvas.m_Target->SetTarget(bitmap.Get()); - canvas.m_Target->Clear(); - - FLOAT x2 = -(FLOAT)rect.left; - FLOAT y2 = -(FLOAT)rect.top; - - for (size_t i = 0; i < m_Effects.size(); ++i) - { - auto& it = m_BaseImage->m_Segments[i]; - const auto& effect = m_Effects[i]; - - Microsoft::WRL::ComPtr image; - effect->GetOutput(image.GetAddressOf()); - - D2D1_RECT_F rect = D2D1::RectF(0.0f, 0.0f, 0.0f, 0.0f); - hr = canvas.m_Target->GetImageLocalBounds(image.Get(), &rect); - if (FAILED(hr)) - { - deleteImage(); - return nullptr; - } - - canvas.m_Target->SetTransform(D2D1::Matrix3x2F::Translation(x2, y2)); - canvas.m_Target->DrawImage(effect.Get(), D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR); // We don't do any scaling with this image, so use the simplest interpolation - - x2 += rect.right; - if (m_BaseImage->GetWidth() >= (it.GetX() + it.GetY())) // only increment y if end of row - { - y2 += rect.bottom; - x2 = 0.0f; - } - } - - canvas.m_Target->Flush(); - d2dbitmap->AddSegment(bitmap, rect); - } - } - - if (didDraw) - { - canvas.m_Target->Flush(); - } - else - { - canvas.EndDraw(); - } - - canvas.m_Target->SetTarget(target.Get()); - canvas.m_Target->SetTransform(transform); - return d2dbitmap; -} - -D2D1_SIZE_F D2DEffectStream::GetSize(const Canvas& canvas) -{ - D2D1_SIZE_F size = D2D1::SizeF(0.0f, 0.0f); - - UINT prevY = 0u; - for (size_t i = 0; i < m_Effects.size(); ++i) - { - const auto& effect = m_Effects[i]; - if (!effect) return size; - - auto& segment = m_BaseImage->m_Segments[i]; - - Microsoft::WRL::ComPtr image; - effect->GetOutput(image.GetAddressOf()); - - D2D1_RECT_F rect = D2D1::RectF(0.0f, 0.0f, 0.0f, 0.0f); - HRESULT hr = canvas.m_Target->GetImageLocalBounds(image.Get(), &rect); - if (FAILED(hr)) return size; - - if (i == 0) - { - size.height = rect.bottom; - size.width = rect.right; - continue; - } - - const UINT y = segment.GetY(); - if (y != prevY) - { - prevY = y; - size.height += rect.bottom; - } - else - { - size.width += rect.right; - } - } - - return size; -} - -D2DEffectStream::D2DEffectStream(D2DBitmap* base) -{ - m_Effects.resize(base->m_Segments.size()); - m_BaseImage = base; -} - -void D2DEffectStream::AddEffect(const Canvas& canvas, const IID& effectId) -{ - for (size_t i = 0; i < m_BaseImage->m_Segments.size(); ++i) - { - auto& segment = m_BaseImage->m_Segments[i]; - Microsoft::WRL::ComPtr effect; - canvas.m_Target->CreateEffect(effectId, effect.GetAddressOf()); - - if (!m_Effects[i]) - { - effect->SetInput(0U, segment.GetBitmap()); - } - else - { - effect->SetInputEffect(0U, m_Effects[i].Get()); - } - - m_Effects[i] = effect; - } -} - -} // namespace Util -} // namespace Gfx +/* Copyright (C) 2018 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#include "StdAfx.h" +#include "D2DEffectStream.h" + +namespace Gfx { +namespace Util { + +const FLOAT PI = 3.14159265f; +constexpr FLOAT ToRadians(FLOAT deg) { return deg * (PI / 180.0f); } + +void D2DEffectStream::Crop(const Canvas& canvas, const D2D1_RECT_F& crop) +{ + AddEffect(canvas, CLSID_D2D1Crop); + for (auto& effect : m_Effects) + { + effect->SetValue(D2D1_CROP_PROP_RECT, crop); + effect->SetValue(D2D1_CROP_PROP_BORDER_MODE, D2D1_BORDER_MODE_SOFT); + } + + AddEffect(canvas, CLSID_D2D12DAffineTransform); + for (auto& effect : m_Effects) + { + effect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX, D2D1::Matrix3x2F::Translation(-crop.left, -crop.top)); + } +} + +void D2DEffectStream::Tint(const Canvas& canvas, const D2D1_MATRIX_5X4_F& matrix) +{ + AddEffect(canvas, CLSID_D2D1ColorMatrix); + for (auto& effect : m_Effects) + { + effect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, matrix); + } +} + +void D2DEffectStream::Rotate(const Canvas& canvas, const FLOAT& angle) +{ + AddEffect(canvas, CLSID_D2D12DAffineTransform); + const auto size = GetSize(canvas); + const FLOAT originalW = size.width; + const FLOAT originalH = size.height; + if (originalW == 0.0f || originalH == 0.0f) return; + + const D2D1_POINT_2F pt = D2D1::Point2F(originalW / 2.0f, originalH / 2.0f); + + for (auto& effect : m_Effects) + { + const FLOAT cos_f = cos(ToRadians(angle)); + const FLOAT sin_f = sin(ToRadians(angle)); + + const FLOAT transformW = fabs(originalW * cos_f) + fabs(originalH * sin_f); + const FLOAT transformH = fabs(originalW * sin_f) + fabs(originalH * cos_f); + + const FLOAT cx = transformW / 2.0f; + const FLOAT cy = transformH / 2.0f; + + effect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX, + D2D1::Matrix3x2F::Rotation(angle, pt) * + D2D1::Matrix3x2F::Translation(cx - pt.x, cy - pt.y)); + + if (fmod(angle, 90.0f) == 0.0f) + { + effect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_BORDER_MODE, D2D1_BORDER_MODE_HARD); + } + } +} + +void D2DEffectStream::Flip(const Canvas& canvas, const FlipType& flipType) +{ + AddEffect(canvas, CLSID_D2D12DAffineTransform); + const auto size = GetSize(canvas); + if (size.width == 0.0f || size.height == 0.0f) return; + + const D2D1_POINT_2F pt = D2D1::Point2F(size.width / 2.0f, size.height / 2.0f); + + for (auto& effect : m_Effects) + { + D2D1_MATRIX_3X2_F transform = D2D1::Matrix3x2F::Identity(); + + switch (flipType) + { + case FlipType::Vertical: transform = D2D1::Matrix3x2F::Scale(1.0f, -1.0f, pt); break; + case FlipType::Horizontal: transform = D2D1::Matrix3x2F::Scale(-1.0f, 1.0f, pt); break; + case FlipType::Both: transform = D2D1::Matrix3x2F::Scale(-1.0f, -1.0f, pt); break; + + case FlipType::None: + default: + continue; + } + + effect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX, transform); + } +} + +void D2DEffectStream::ApplyExifOrientation(const Canvas& canvas) +{ + switch (m_BaseImage->GetOrientation()) + { + case 2: Flip(canvas, FlipType::Horizontal); break; + case 3: Rotate(canvas, 180.0f); break; + case 4: Flip(canvas, FlipType::Vertical); break; + case 5: Flip(canvas, FlipType::Horizontal); Rotate(canvas, 270.0f); break; + case 6: Rotate(canvas, 90.0f); break; + case 7: Flip(canvas, FlipType::Horizontal); Rotate(canvas, 90.0f); break; + case 8: Rotate(canvas, 270.0f); break; + } +} + +D2DBitmap* D2DEffectStream::ToBitmap(Canvas& canvas, const D2D1_SIZE_F* imageSize) +{ + bool changed = false; + for (const auto& effect : m_Effects) + { + changed |= !!effect; + } + if (!changed) return m_BaseImage; + + const D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)); + + Microsoft::WRL::ComPtr target; + canvas.m_Target->GetTarget(target.GetAddressOf()); + + D2D1_MATRIX_3X2_F transform = D2D1::Matrix3x2F::Identity(); + canvas.m_Target->GetTransform(&transform); + + const UINT maxBitmapSize = (UINT)canvas.m_MaxBitmapSize; + const auto size = (imageSize) ? *imageSize : GetSize(canvas); + if (size.width < 0.0f || size.height < 0.0f) return nullptr; + + const UINT width = (UINT)size.width; + const UINT height = (UINT)size.height; + + D2DBitmap* d2dbitmap = new D2DBitmap(m_BaseImage->m_Path, m_BaseImage->m_ExifOrientation); + d2dbitmap->m_Width = width; + d2dbitmap->m_Height = height; + + auto deleteImage = [&d2dbitmap]() -> void + { + delete d2dbitmap; + d2dbitmap = nullptr; + }; + + auto didDraw = canvas.IsDrawing(); + if (didDraw) + { + canvas.m_Target->Flush(); + } + else + { + canvas.BeginDraw(); + } + + for (UINT y = 0U, H = height / maxBitmapSize; y <= H; ++y) + { + for (UINT x = 0U, W = width / maxBitmapSize; x <= W; ++x) + { + D2D1_RECT_U rect = D2D1::RectU( + (x * maxBitmapSize), + (y * maxBitmapSize), + (x == W ? (width - maxBitmapSize * x) : maxBitmapSize), // If last x coordinate, find cutoff + (y == H ? (height - maxBitmapSize * y) : maxBitmapSize)); // If last y coordinate, find cutoff + + Microsoft::WRL::ComPtr bitmap; + HRESULT hr = canvas.m_Target->CreateBitmap( + D2D1::SizeU(rect.right, rect.bottom), + nullptr, + 0U, + props, + bitmap.GetAddressOf()); + if (FAILED(hr)) + { + canvas.EndDraw(); + canvas.m_Target->SetTarget(target.Get()); + canvas.m_Target->SetTransform(transform); + deleteImage(); + return nullptr; + } + + canvas.m_Target->SetTarget(bitmap.Get()); + canvas.m_Target->Clear(); + + FLOAT x2 = -(FLOAT)rect.left; + FLOAT y2 = -(FLOAT)rect.top; + + for (size_t i = 0; i < m_Effects.size(); ++i) + { + auto& it = m_BaseImage->m_Segments[i]; + const auto& effect = m_Effects[i]; + + Microsoft::WRL::ComPtr image; + effect->GetOutput(image.GetAddressOf()); + + D2D1_RECT_F rect = D2D1::RectF(0.0f, 0.0f, 0.0f, 0.0f); + hr = canvas.m_Target->GetImageLocalBounds(image.Get(), &rect); + if (FAILED(hr)) + { + deleteImage(); + return nullptr; + } + + canvas.m_Target->SetTransform(D2D1::Matrix3x2F::Translation(x2, y2)); + canvas.m_Target->DrawImage(effect.Get(), D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR); // We don't do any scaling with this image, so use the simplest interpolation + + x2 += rect.right; + if (m_BaseImage->GetWidth() >= (it.GetX() + it.GetY())) // only increment y if end of row + { + y2 += rect.bottom; + x2 = 0.0f; + } + } + + canvas.m_Target->Flush(); + d2dbitmap->AddSegment(bitmap, rect); + } + } + + if (didDraw) + { + canvas.m_Target->Flush(); + } + else + { + canvas.EndDraw(); + } + + canvas.m_Target->SetTarget(target.Get()); + canvas.m_Target->SetTransform(transform); + return d2dbitmap; +} + +D2D1_SIZE_F D2DEffectStream::GetSize(const Canvas& canvas) +{ + D2D1_SIZE_F size = D2D1::SizeF(0.0f, 0.0f); + + UINT prevY = 0u; + for (size_t i = 0; i < m_Effects.size(); ++i) + { + const auto& effect = m_Effects[i]; + if (!effect) return size; + + auto& segment = m_BaseImage->m_Segments[i]; + + Microsoft::WRL::ComPtr image; + effect->GetOutput(image.GetAddressOf()); + + D2D1_RECT_F rect = D2D1::RectF(0.0f, 0.0f, 0.0f, 0.0f); + HRESULT hr = canvas.m_Target->GetImageLocalBounds(image.Get(), &rect); + if (FAILED(hr)) return size; + + if (i == 0) + { + size.height = rect.bottom; + size.width = rect.right; + continue; + } + + const UINT y = segment.GetY(); + if (y != prevY) + { + prevY = y; + size.height += rect.bottom; + } + else + { + size.width += rect.right; + } + } + + return size; +} + +D2DEffectStream::D2DEffectStream(D2DBitmap* base) +{ + m_Effects.resize(base->m_Segments.size()); + m_BaseImage = base; +} + +void D2DEffectStream::AddEffect(const Canvas& canvas, const IID& effectId) +{ + for (size_t i = 0; i < m_BaseImage->m_Segments.size(); ++i) + { + auto& segment = m_BaseImage->m_Segments[i]; + Microsoft::WRL::ComPtr effect; + canvas.m_Target->CreateEffect(effectId, effect.GetAddressOf()); + + if (!m_Effects[i]) + { + effect->SetInput(0U, segment.GetBitmap()); + } + else + { + effect->SetInputEffect(0U, m_Effects[i].Get()); + } + + m_Effects[i] = effect; + } +} + +} // namespace Util +} // namespace Gfx diff --git a/Common/Gfx/Util/D2DEffectStream.h b/Common/Gfx/Util/D2DEffectStream.h index 0c5b478b3..003219e96 100644 --- a/Common/Gfx/Util/D2DEffectStream.h +++ b/Common/Gfx/Util/D2DEffectStream.h @@ -1,50 +1,50 @@ -/* Copyright (C) 2018 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#ifndef RM_GFX_UTIL_D2DEFFECTSTREAM_H_ -#define RM_GFX_UTIL_D2DEFFECTSTREAM_H_ - -#include "../D2DBitmap.h" - -namespace Gfx { -namespace Util { - -enum class FlipType : UINT -{ - None, - Vertical, - Horizontal, - Both -}; - -class D2DEffectStream -{ -public: - void Crop(const Canvas& canvas, const D2D1_RECT_F& crop); - void Tint(const Canvas& canvas, const D2D1_MATRIX_5X4_F& matrix); - void Rotate(const Canvas& canvas, const FLOAT& angle); - void Flip(const Canvas& canvas, const FlipType& flipType); - void ApplyExifOrientation(const Canvas& canvas); - D2DBitmap* ToBitmap(Canvas& canvas, const D2D1_SIZE_F* imageSize); - D2D1_SIZE_F GetSize(const Canvas& canvas); - -private: - friend class Canvas; - friend class D2DBitmap; - - D2DEffectStream(Gfx::D2DBitmap* base); - - void AddEffect(const Canvas& canvas, const IID& effectId); - - std::vector> m_Effects; - Gfx::D2DBitmap* m_BaseImage; -}; - -} // namespace Util -} // namespace Gfx - -#endif +/* Copyright (C) 2018 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#ifndef RM_GFX_UTIL_D2DEFFECTSTREAM_H_ +#define RM_GFX_UTIL_D2DEFFECTSTREAM_H_ + +#include "../D2DBitmap.h" + +namespace Gfx { +namespace Util { + +enum class FlipType : UINT +{ + None, + Vertical, + Horizontal, + Both +}; + +class D2DEffectStream +{ +public: + void Crop(const Canvas& canvas, const D2D1_RECT_F& crop); + void Tint(const Canvas& canvas, const D2D1_MATRIX_5X4_F& matrix); + void Rotate(const Canvas& canvas, const FLOAT& angle); + void Flip(const Canvas& canvas, const FlipType& flipType); + void ApplyExifOrientation(const Canvas& canvas); + D2DBitmap* ToBitmap(Canvas& canvas, const D2D1_SIZE_F* imageSize); + D2D1_SIZE_F GetSize(const Canvas& canvas); + +private: + friend class Canvas; + friend class D2DBitmap; + + D2DEffectStream(Gfx::D2DBitmap* base); + + void AddEffect(const Canvas& canvas, const IID& effectId); + + std::vector> m_Effects; + Gfx::D2DBitmap* m_BaseImage; +}; + +} // namespace Util +} // namespace Gfx + +#endif diff --git a/Common/ScopedFunction.h b/Common/ScopedFunction.h index f98646643..bdfc22e4a 100644 --- a/Common/ScopedFunction.h +++ b/Common/ScopedFunction.h @@ -1,39 +1,39 @@ -/* Copyright (C) 2015 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#ifndef RM_COMMON_SCOPEDFUNCTION_H_ -#define RM_COMMON_SCOPEDFUNCTION_H_ - -// Executes function T when the ScopedFunction is destructed. -template -class ScopedFunction -{ -public: - explicit ScopedFunction(T&& func) : m_Func(std::move(func)) {} - explicit ScopedFunction(const T&) = delete; - - ~ScopedFunction() - { - m_Func(); - } - - void operator=(T&&) = delete; - void operator=(const T&) = delete; - -private: - T m_Func; -}; - -// Helper to create ScopedFunction instances. Use as follows: -// auto scopedFunction = Scoped([&] { work(); }); -template -ScopedFunction Scoped(T t) -{ - return ScopedFunction(std::move(t)); -} - -#endif +/* Copyright (C) 2015 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#ifndef RM_COMMON_SCOPEDFUNCTION_H_ +#define RM_COMMON_SCOPEDFUNCTION_H_ + +// Executes function T when the ScopedFunction is destructed. +template +class ScopedFunction +{ +public: + explicit ScopedFunction(T&& func) : m_Func(std::move(func)) {} + explicit ScopedFunction(const T&) = delete; + + ~ScopedFunction() + { + m_Func(); + } + + void operator=(T&&) = delete; + void operator=(const T&) = delete; + +private: + T m_Func; +}; + +// Helper to create ScopedFunction instances. Use as follows: +// auto scopedFunction = Scoped([&] { work(); }); +template +ScopedFunction Scoped(T t) +{ + return ScopedFunction(std::move(t)); +} + +#endif diff --git a/Common/StdAfx.cpp b/Common/StdAfx.cpp index 04f03a24b..51e29d4fd 100644 --- a/Common/StdAfx.cpp +++ b/Common/StdAfx.cpp @@ -1,9 +1,9 @@ -/* Copyright (C) 2014 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#include "StdAfx.h" -#include "StdAfx.h" +/* Copyright (C) 2014 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#include "StdAfx.h" +#include "StdAfx.h" diff --git a/Common/StdAfx.h b/Common/StdAfx.h index e90a375c1..edd70ccb3 100644 --- a/Common/StdAfx.h +++ b/Common/StdAfx.h @@ -1,37 +1,37 @@ -/* Copyright (C) 2014 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#ifndef __STDAFX_H__ -#define __STDAFX_H__ - -// Common is used by projects that don't link to msvcpNNN.dll at all so this header should include -// only C compatible headers. - -#define _CRTDBG_MAP_ALLOC -#include - -#include -#include -#include -#include -#include -#include // For Gdiplus.h. -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#endif +/* Copyright (C) 2014 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#ifndef __STDAFX_H__ +#define __STDAFX_H__ + +// Common is used by projects that don't link to msvcpNNN.dll at all so this header should include +// only C compatible headers. + +#define _CRTDBG_MAP_ALLOC +#include + +#include +#include +#include +#include +#include +#include // For Gdiplus.h. +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#endif diff --git a/Language/NorwegianBokmal.h b/Language/NorwegianBokmal.h index 50835f04a..44009e29c 100644 Binary files a/Language/NorwegianBokmal.h and b/Language/NorwegianBokmal.h differ diff --git a/Library/GeneralImage.cpp b/Library/GeneralImage.cpp index 00f141ff8..7262528b5 100644 --- a/Library/GeneralImage.cpp +++ b/Library/GeneralImage.cpp @@ -1,448 +1,448 @@ -/* Copyright (C) 2018 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#include "StdAfx.h" -#include "GeneralImage.h" -#include "Logger.h" -#include "../Common/PathUtil.h" - -// GrayScale Matrix -const D2D1_MATRIX_5X4_F GeneralImage::c_GreyScaleMatrix = { - 0.299f, 0.299f, 0.299f, 0.0f, - 0.587f, 0.587f, 0.587f, 0.0f, - 0.114f, 0.114f, 0.114f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 0.0f, 0.0f -}; - - -const D2D1_MATRIX_5X4_F GeneralImage::c_IdentityMatrix = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 0.0f, 0.0f -}; - -GeneralImageHelper_DefineOptionArray(GeneralImage::c_DefaultOptionArray, L""); - -GeneralImage::GeneralImage(const WCHAR* name, const WCHAR** optionArray, bool disableTransform, Skin* skin) : - m_Bitmap(nullptr), - m_BitmapProcessed(nullptr), - m_Skin(skin), - m_Name(name ? name : L"ImageName"), - m_OptionArray(optionArray ? optionArray : c_DefaultOptionArray), - m_DisableTransform(disableTransform), - m_Options() -{ -} - -GeneralImage::~GeneralImage() -{ - DisposeImage(); -} - -void GeneralImage::DisposeImage() -{ - if (m_Bitmap) - { - delete m_Bitmap; - m_Bitmap = nullptr; - } - - if (m_BitmapProcessed) - { - delete m_BitmapProcessed; - m_BitmapProcessed = nullptr; - } -} - -void GeneralImage::ReadOptions(ConfigParser& parser, const WCHAR* section, const WCHAR* imagePath) -{ - m_Path = parser.ReadString(section, m_OptionArray[OptionIndexImagePath], imagePath); - PathUtil::AppendBackslashIfMissing(m_Path); - - if (!m_DisableTransform) - { - m_Options.m_Crop.left = m_Options.m_Crop.top = m_Options.m_Crop.right = m_Options.m_Crop.bottom = -1; - m_Options.m_CropMode = ImageOptions::CROPMODE_TL; - - const std::wstring& crop = parser.ReadString(section, m_OptionArray[OptionIndexImageCrop], L""); - if (!crop.empty()) - { - if (wcschr(crop.c_str(), L',')) - { - WCHAR* context = nullptr; - WCHAR* parseSz = _wcsdup(crop.c_str()); - WCHAR* token; - - token = wcstok(parseSz, L",", &context); - if (token) - { - m_Options.m_Crop.left = (FLOAT)parser.ParseInt(token, 0); - - token = wcstok(nullptr, L",", &context); - if (token) - { - m_Options.m_Crop.top = (FLOAT)parser.ParseInt(token, 0); - - token = wcstok(nullptr, L",", &context); - if (token) - { - m_Options.m_Crop.right = (FLOAT)parser.ParseInt(token, 0) + m_Options.m_Crop.left; - - token = wcstok(nullptr, L",", &context); - if (token) - { - m_Options.m_Crop.bottom = (FLOAT)parser.ParseInt(token, 0) + m_Options.m_Crop.top; - - token = wcstok(nullptr, L",", &context); - if (token) - { - m_Options.m_CropMode = (ImageOptions::CROPMODE)parser.ParseInt(token, 0); - } - } - } - } - } - free(parseSz); - } - - if (m_Options.m_CropMode < ImageOptions::CROPMODE_TL || m_Options.m_CropMode > ImageOptions::CROPMODE_C) - { - m_Options.m_CropMode = ImageOptions::CROPMODE_TL; - LogErrorF(m_Skin, L"%s=%s (origin) is not valid in [%s]", m_OptionArray[OptionIndexImageCrop], crop, section); - } - } - } - - m_Options.m_GreyScale = parser.ReadBool(section, m_OptionArray[OptionIndexGreyscale], false); - - D2D1_COLOR_F tint = parser.ReadColor(section, m_OptionArray[OptionIndexImageTint], D2D1::ColorF(D2D1::ColorF::White)); - int alpha = parser.ReadInt(section, m_OptionArray[OptionIndexImageAlpha], (INT)(tint.a * 255)); // for backwards compatibility - alpha = min(255, alpha); - alpha = max(0, alpha); - - m_Options.m_ColorMatrix = c_IdentityMatrix; - - // Read in the Color Matrix - // It has to be read in like this because it crashes when reading over 17 floats - // at one time. The parser does it fine, but after putting the returned values - // into the Color Matrix the next time the parser is used it crashes. - // Note: is this still relevant? Kept for BWC - std::vector matrix1 = parser.ReadFloats(section, m_OptionArray[OptionIndexColorMatrix1]); - if (matrix1.size() == 5) - { - for (int i = 0; i < 4; ++i) // The fifth column must be 0. - { - m_Options.m_ColorMatrix.m[0][i] = matrix1[i]; - } - } - else - { - m_Options.m_ColorMatrix.m[0][0] = tint.r; - } - - std::vector matrix2 = parser.ReadFloats(section, m_OptionArray[OptionIndexColorMatrix2]); - if (matrix2.size() == 5) - { - for (int i = 0; i < 4; ++i) // The fifth column must be 0. - { - m_Options.m_ColorMatrix.m[1][i] = matrix2[i]; - } - } - else - { - m_Options.m_ColorMatrix.m[1][1] = tint.g; - } - - std::vector matrix3 = parser.ReadFloats(section, m_OptionArray[OptionIndexColorMatrix3]); - if (matrix3.size() == 5) - { - for (int i = 0; i < 4; ++i) // The fifth column must be 0. - { - m_Options.m_ColorMatrix.m[2][i] = matrix3[i]; - } - } - else - { - m_Options.m_ColorMatrix.m[2][2] = tint.b; - } - - std::vector matrix4 = parser.ReadFloats(section, m_OptionArray[OptionIndexColorMatrix4]); - if (matrix4.size() == 5) - { - for (int i = 0; i < 4; ++i) // The fifth column must be 0. - { - m_Options.m_ColorMatrix.m[3][i] = matrix4[i]; - } - } - else - { - m_Options.m_ColorMatrix.m[3][3] = alpha / 255.0f; - } - - std::vector matrix5 = parser.ReadFloats(section, m_OptionArray[OptionIndexColorMatrix5]); - if (matrix5.size() == 5) - { - for (int i = 0; i < 4; ++i) // The fifth column must be 1. - { - m_Options.m_ColorMatrix.m[4][i] = matrix5[i]; - } - } - - const WCHAR* flip = parser.ReadString(section, m_OptionArray[OptionIndexImageFlip], L"NONE").c_str(); - if (_wcsicmp(flip, L"NONE") == 0) - { - m_Options.m_Flip = Gfx::Util::FlipType::None; - } - else if (_wcsicmp(flip, L"HORIZONTAL") == 0) - { - m_Options.m_Flip = Gfx::Util::FlipType::Horizontal; - } - else if (_wcsicmp(flip, L"VERTICAL") == 0) - { - m_Options.m_Flip = Gfx::Util::FlipType::Vertical; - } - else if (_wcsicmp(flip, L"BOTH") == 0) - { - m_Options.m_Flip = Gfx::Util::FlipType::Both; - } - else - { - LogErrorF(m_Skin, L"%s=%s (origin) is not valid in [%s]", m_OptionArray[OptionIndexImageFlip], flip, section); - } - - if (!m_DisableTransform) - { - m_Options.m_Rotate = (FLOAT)parser.ReadFloat(section, m_OptionArray[OptionIndexImageRotate], 0.0); - } - - m_Options.m_UseExifOrientation = parser.ReadBool(section, m_OptionArray[OptionIndexUseExifOrientation], false); -} - -bool GeneralImage::LoadImage(const std::wstring& imageName) -{ - if (!m_Skin || imageName.empty()) - { - DisposeImage(); - return false; - } - - std::wstring filename = m_Path + imageName; - m_Skin->MakePathAbsolute(filename); - - // Check extension and if it is missing, add .png - size_t pos = filename.rfind(L'\\'); - if (filename.find(L'.', (pos == std::wstring::npos) ? 0 : pos + 1) == std::wstring::npos) - { - filename += L".png"; - } - - if (m_Bitmap && !m_Bitmap->GetBitmap()->HasFileChanged(filename)) - { - ApplyTransforms(); - return true; - } - - ImageOptions info; - Gfx::D2DBitmap::GetFileInfo(filename, &info); - - if (!info.isValid()) - { - DisposeImage(); - return false; - } - - ImageCacheHandle* handle = GetImageCache().Get(info); - if (!handle) - { - auto bitmap = new Gfx::D2DBitmap(filename); - - HRESULT hr = bitmap->Load(m_Skin->GetCanvas()); - if (SUCCEEDED(hr)) - { - GetImageCache().Put(info, bitmap); - handle = GetImageCache().Get(info); - if (!handle) return false; - } - else - { - delete bitmap; - bitmap = nullptr; - } - } - - DisposeImage(); - - if (handle) - { - m_Bitmap = handle; - - m_Options.m_Path = info.m_Path; - m_Options.m_FileSize = info.m_FileSize; - m_Options.m_FileTime = info.m_FileTime; - - ApplyTransforms(); - return true; - } - - return false; -} - -D2D1_SIZE_F GeneralImage::ApplyCrop(Gfx::Util::D2DEffectStream* stream, Gfx::D2DBitmap* bitmap) const -{ - const FLOAT imageW = (FLOAT)bitmap->GetWidth(); - const FLOAT imageH = (FLOAT)bitmap->GetHeight(); - - auto& canvas = m_Skin->GetCanvas(); - - // Make sure to get the any size changes from EXIF data - auto size = stream->GetSize(canvas); - if (size.width <= 0.0f && size.height <= 0.0f) - { - size.width = imageW; - size.height = imageH; - } - - const auto& crop = m_Options.m_Crop; - if (crop.right == -1.0f && crop.left == -1.0f && crop.top == -1.0f && crop.bottom == -1.0f) - { - return size; - } - - if (crop.right - crop.left >= 0.0f && crop.bottom - crop.top >= 0.0f) - { - FLOAT x = 0.0f; - FLOAT y = 0.0f; - - switch (m_Options.m_CropMode) - { - case ImageOptions::CROPMODE_TL: - default: - x = crop.left; - y = crop.top; - break; - - case ImageOptions::CROPMODE_TR: - x = crop.left + imageW; - y = crop.top; - break; - - case ImageOptions::CROPMODE_BR: - x = crop.left + imageW; - y = crop.top + imageH; - break; - - case ImageOptions::CROPMODE_BL: - x = crop.left; - y = crop.top + imageH; - break; - - case ImageOptions::CROPMODE_C: - x = crop.left + (imageW / 2.0f); - y = crop.top + (imageH / 2.0f); - break; - } - - const D2D1_RECT_F rect = D2D1::RectF(x, y, crop.right - crop.left + x, crop.bottom - crop.top + y); - stream->Crop(canvas, rect); - - size.width = rect.right - rect.left; - size.height = rect.bottom - rect.top; - } - - return size; -} - -void GeneralImage::ApplyTransforms() -{ - if (m_BitmapProcessed && m_BitmapProcessed->GetKey() == m_Options) return; - - if (m_BitmapProcessed) - { - delete m_BitmapProcessed; - m_BitmapProcessed = nullptr; - } - - ImageCacheHandle* handle = GetImageCache().Get(m_Options); - if (!handle) - { - auto* bitmap = m_Bitmap->GetBitmap(); - auto& canvas = m_Skin->GetCanvas(); - auto* stream = bitmap->CreateEffectStream(); - - // To preserve backwards compatibility, apply transforms in the following order: - // 1. Exif orientation - // 2. Crop - // 3. Tinting (greyscale first, then color matrix) - // 4. Transforms (GDI+ flips, then rotates) - - if (m_Options.m_UseExifOrientation) stream->ApplyExifOrientation(canvas); - - const auto crop = ApplyCrop(stream, bitmap); - auto* croppedBitmap = stream->ToBitmap(canvas, &crop); - if (!croppedBitmap) - { - delete stream; - stream = nullptr; - return; - } - - if (croppedBitmap != bitmap) - { - delete stream; - stream = croppedBitmap->CreateEffectStream(); - } - - if (m_Options.m_GreyScale) stream->Tint(canvas, c_GreyScaleMatrix); - - if (!CompareColorMatrix(m_Options.m_ColorMatrix, c_IdentityMatrix)) stream->Tint(canvas, m_Options.m_ColorMatrix); - - stream->Flip(canvas, m_Options.m_Flip); - - if (m_Options.m_Rotate != 0.0f) stream->Rotate(canvas, m_Options.m_Rotate); - - auto* newBitmap = stream->ToBitmap(canvas, nullptr); - - delete stream; - stream = nullptr; - - if (croppedBitmap != bitmap) - { - delete croppedBitmap; - croppedBitmap = nullptr; - } - - if (newBitmap != nullptr) - { - GetImageCache().Put(m_Options, newBitmap); - handle = GetImageCache().Get(m_Options); - if (!handle) return; - } - } - - if (handle) - { - m_BitmapProcessed = handle; - } -} - -bool GeneralImage::CompareColorMatrix(const D2D1_MATRIX_5X4_F& a, const D2D1_MATRIX_5X4_F& b) -{ - for (int i = 0; i < 5; ++i) - { - for (int j = 0; j < 4; ++j) - { - if (a.m[i][j] != b.m[i][j]) - { - return false; - } - } - } - return true; -} +/* Copyright (C) 2018 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#include "StdAfx.h" +#include "GeneralImage.h" +#include "Logger.h" +#include "../Common/PathUtil.h" + +// GrayScale Matrix +const D2D1_MATRIX_5X4_F GeneralImage::c_GreyScaleMatrix = { + 0.299f, 0.299f, 0.299f, 0.0f, + 0.587f, 0.587f, 0.587f, 0.0f, + 0.114f, 0.114f, 0.114f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 0.0f +}; + + +const D2D1_MATRIX_5X4_F GeneralImage::c_IdentityMatrix = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 0.0f +}; + +GeneralImageHelper_DefineOptionArray(GeneralImage::c_DefaultOptionArray, L""); + +GeneralImage::GeneralImage(const WCHAR* name, const WCHAR** optionArray, bool disableTransform, Skin* skin) : + m_Bitmap(nullptr), + m_BitmapProcessed(nullptr), + m_Skin(skin), + m_Name(name ? name : L"ImageName"), + m_OptionArray(optionArray ? optionArray : c_DefaultOptionArray), + m_DisableTransform(disableTransform), + m_Options() +{ +} + +GeneralImage::~GeneralImage() +{ + DisposeImage(); +} + +void GeneralImage::DisposeImage() +{ + if (m_Bitmap) + { + delete m_Bitmap; + m_Bitmap = nullptr; + } + + if (m_BitmapProcessed) + { + delete m_BitmapProcessed; + m_BitmapProcessed = nullptr; + } +} + +void GeneralImage::ReadOptions(ConfigParser& parser, const WCHAR* section, const WCHAR* imagePath) +{ + m_Path = parser.ReadString(section, m_OptionArray[OptionIndexImagePath], imagePath); + PathUtil::AppendBackslashIfMissing(m_Path); + + if (!m_DisableTransform) + { + m_Options.m_Crop.left = m_Options.m_Crop.top = m_Options.m_Crop.right = m_Options.m_Crop.bottom = -1; + m_Options.m_CropMode = ImageOptions::CROPMODE_TL; + + const std::wstring& crop = parser.ReadString(section, m_OptionArray[OptionIndexImageCrop], L""); + if (!crop.empty()) + { + if (wcschr(crop.c_str(), L',')) + { + WCHAR* context = nullptr; + WCHAR* parseSz = _wcsdup(crop.c_str()); + WCHAR* token; + + token = wcstok(parseSz, L",", &context); + if (token) + { + m_Options.m_Crop.left = (FLOAT)parser.ParseInt(token, 0); + + token = wcstok(nullptr, L",", &context); + if (token) + { + m_Options.m_Crop.top = (FLOAT)parser.ParseInt(token, 0); + + token = wcstok(nullptr, L",", &context); + if (token) + { + m_Options.m_Crop.right = (FLOAT)parser.ParseInt(token, 0) + m_Options.m_Crop.left; + + token = wcstok(nullptr, L",", &context); + if (token) + { + m_Options.m_Crop.bottom = (FLOAT)parser.ParseInt(token, 0) + m_Options.m_Crop.top; + + token = wcstok(nullptr, L",", &context); + if (token) + { + m_Options.m_CropMode = (ImageOptions::CROPMODE)parser.ParseInt(token, 0); + } + } + } + } + } + free(parseSz); + } + + if (m_Options.m_CropMode < ImageOptions::CROPMODE_TL || m_Options.m_CropMode > ImageOptions::CROPMODE_C) + { + m_Options.m_CropMode = ImageOptions::CROPMODE_TL; + LogErrorF(m_Skin, L"%s=%s (origin) is not valid in [%s]", m_OptionArray[OptionIndexImageCrop], crop, section); + } + } + } + + m_Options.m_GreyScale = parser.ReadBool(section, m_OptionArray[OptionIndexGreyscale], false); + + D2D1_COLOR_F tint = parser.ReadColor(section, m_OptionArray[OptionIndexImageTint], D2D1::ColorF(D2D1::ColorF::White)); + int alpha = parser.ReadInt(section, m_OptionArray[OptionIndexImageAlpha], (INT)(tint.a * 255)); // for backwards compatibility + alpha = min(255, alpha); + alpha = max(0, alpha); + + m_Options.m_ColorMatrix = c_IdentityMatrix; + + // Read in the Color Matrix + // It has to be read in like this because it crashes when reading over 17 floats + // at one time. The parser does it fine, but after putting the returned values + // into the Color Matrix the next time the parser is used it crashes. + // Note: is this still relevant? Kept for BWC + std::vector matrix1 = parser.ReadFloats(section, m_OptionArray[OptionIndexColorMatrix1]); + if (matrix1.size() == 5) + { + for (int i = 0; i < 4; ++i) // The fifth column must be 0. + { + m_Options.m_ColorMatrix.m[0][i] = matrix1[i]; + } + } + else + { + m_Options.m_ColorMatrix.m[0][0] = tint.r; + } + + std::vector matrix2 = parser.ReadFloats(section, m_OptionArray[OptionIndexColorMatrix2]); + if (matrix2.size() == 5) + { + for (int i = 0; i < 4; ++i) // The fifth column must be 0. + { + m_Options.m_ColorMatrix.m[1][i] = matrix2[i]; + } + } + else + { + m_Options.m_ColorMatrix.m[1][1] = tint.g; + } + + std::vector matrix3 = parser.ReadFloats(section, m_OptionArray[OptionIndexColorMatrix3]); + if (matrix3.size() == 5) + { + for (int i = 0; i < 4; ++i) // The fifth column must be 0. + { + m_Options.m_ColorMatrix.m[2][i] = matrix3[i]; + } + } + else + { + m_Options.m_ColorMatrix.m[2][2] = tint.b; + } + + std::vector matrix4 = parser.ReadFloats(section, m_OptionArray[OptionIndexColorMatrix4]); + if (matrix4.size() == 5) + { + for (int i = 0; i < 4; ++i) // The fifth column must be 0. + { + m_Options.m_ColorMatrix.m[3][i] = matrix4[i]; + } + } + else + { + m_Options.m_ColorMatrix.m[3][3] = alpha / 255.0f; + } + + std::vector matrix5 = parser.ReadFloats(section, m_OptionArray[OptionIndexColorMatrix5]); + if (matrix5.size() == 5) + { + for (int i = 0; i < 4; ++i) // The fifth column must be 1. + { + m_Options.m_ColorMatrix.m[4][i] = matrix5[i]; + } + } + + const WCHAR* flip = parser.ReadString(section, m_OptionArray[OptionIndexImageFlip], L"NONE").c_str(); + if (_wcsicmp(flip, L"NONE") == 0) + { + m_Options.m_Flip = Gfx::Util::FlipType::None; + } + else if (_wcsicmp(flip, L"HORIZONTAL") == 0) + { + m_Options.m_Flip = Gfx::Util::FlipType::Horizontal; + } + else if (_wcsicmp(flip, L"VERTICAL") == 0) + { + m_Options.m_Flip = Gfx::Util::FlipType::Vertical; + } + else if (_wcsicmp(flip, L"BOTH") == 0) + { + m_Options.m_Flip = Gfx::Util::FlipType::Both; + } + else + { + LogErrorF(m_Skin, L"%s=%s (origin) is not valid in [%s]", m_OptionArray[OptionIndexImageFlip], flip, section); + } + + if (!m_DisableTransform) + { + m_Options.m_Rotate = (FLOAT)parser.ReadFloat(section, m_OptionArray[OptionIndexImageRotate], 0.0); + } + + m_Options.m_UseExifOrientation = parser.ReadBool(section, m_OptionArray[OptionIndexUseExifOrientation], false); +} + +bool GeneralImage::LoadImage(const std::wstring& imageName) +{ + if (!m_Skin || imageName.empty()) + { + DisposeImage(); + return false; + } + + std::wstring filename = m_Path + imageName; + m_Skin->MakePathAbsolute(filename); + + // Check extension and if it is missing, add .png + size_t pos = filename.rfind(L'\\'); + if (filename.find(L'.', (pos == std::wstring::npos) ? 0 : pos + 1) == std::wstring::npos) + { + filename += L".png"; + } + + if (m_Bitmap && !m_Bitmap->GetBitmap()->HasFileChanged(filename)) + { + ApplyTransforms(); + return true; + } + + ImageOptions info; + Gfx::D2DBitmap::GetFileInfo(filename, &info); + + if (!info.isValid()) + { + DisposeImage(); + return false; + } + + ImageCacheHandle* handle = GetImageCache().Get(info); + if (!handle) + { + auto bitmap = new Gfx::D2DBitmap(filename); + + HRESULT hr = bitmap->Load(m_Skin->GetCanvas()); + if (SUCCEEDED(hr)) + { + GetImageCache().Put(info, bitmap); + handle = GetImageCache().Get(info); + if (!handle) return false; + } + else + { + delete bitmap; + bitmap = nullptr; + } + } + + DisposeImage(); + + if (handle) + { + m_Bitmap = handle; + + m_Options.m_Path = info.m_Path; + m_Options.m_FileSize = info.m_FileSize; + m_Options.m_FileTime = info.m_FileTime; + + ApplyTransforms(); + return true; + } + + return false; +} + +D2D1_SIZE_F GeneralImage::ApplyCrop(Gfx::Util::D2DEffectStream* stream, Gfx::D2DBitmap* bitmap) const +{ + const FLOAT imageW = (FLOAT)bitmap->GetWidth(); + const FLOAT imageH = (FLOAT)bitmap->GetHeight(); + + auto& canvas = m_Skin->GetCanvas(); + + // Make sure to get the any size changes from EXIF data + auto size = stream->GetSize(canvas); + if (size.width <= 0.0f && size.height <= 0.0f) + { + size.width = imageW; + size.height = imageH; + } + + const auto& crop = m_Options.m_Crop; + if (crop.right == -1.0f && crop.left == -1.0f && crop.top == -1.0f && crop.bottom == -1.0f) + { + return size; + } + + if (crop.right - crop.left >= 0.0f && crop.bottom - crop.top >= 0.0f) + { + FLOAT x = 0.0f; + FLOAT y = 0.0f; + + switch (m_Options.m_CropMode) + { + case ImageOptions::CROPMODE_TL: + default: + x = crop.left; + y = crop.top; + break; + + case ImageOptions::CROPMODE_TR: + x = crop.left + imageW; + y = crop.top; + break; + + case ImageOptions::CROPMODE_BR: + x = crop.left + imageW; + y = crop.top + imageH; + break; + + case ImageOptions::CROPMODE_BL: + x = crop.left; + y = crop.top + imageH; + break; + + case ImageOptions::CROPMODE_C: + x = crop.left + (imageW / 2.0f); + y = crop.top + (imageH / 2.0f); + break; + } + + const D2D1_RECT_F rect = D2D1::RectF(x, y, crop.right - crop.left + x, crop.bottom - crop.top + y); + stream->Crop(canvas, rect); + + size.width = rect.right - rect.left; + size.height = rect.bottom - rect.top; + } + + return size; +} + +void GeneralImage::ApplyTransforms() +{ + if (m_BitmapProcessed && m_BitmapProcessed->GetKey() == m_Options) return; + + if (m_BitmapProcessed) + { + delete m_BitmapProcessed; + m_BitmapProcessed = nullptr; + } + + ImageCacheHandle* handle = GetImageCache().Get(m_Options); + if (!handle) + { + auto* bitmap = m_Bitmap->GetBitmap(); + auto& canvas = m_Skin->GetCanvas(); + auto* stream = bitmap->CreateEffectStream(); + + // To preserve backwards compatibility, apply transforms in the following order: + // 1. Exif orientation + // 2. Crop + // 3. Tinting (greyscale first, then color matrix) + // 4. Transforms (GDI+ flips, then rotates) + + if (m_Options.m_UseExifOrientation) stream->ApplyExifOrientation(canvas); + + const auto crop = ApplyCrop(stream, bitmap); + auto* croppedBitmap = stream->ToBitmap(canvas, &crop); + if (!croppedBitmap) + { + delete stream; + stream = nullptr; + return; + } + + if (croppedBitmap != bitmap) + { + delete stream; + stream = croppedBitmap->CreateEffectStream(); + } + + if (m_Options.m_GreyScale) stream->Tint(canvas, c_GreyScaleMatrix); + + if (!CompareColorMatrix(m_Options.m_ColorMatrix, c_IdentityMatrix)) stream->Tint(canvas, m_Options.m_ColorMatrix); + + stream->Flip(canvas, m_Options.m_Flip); + + if (m_Options.m_Rotate != 0.0f) stream->Rotate(canvas, m_Options.m_Rotate); + + auto* newBitmap = stream->ToBitmap(canvas, nullptr); + + delete stream; + stream = nullptr; + + if (croppedBitmap != bitmap) + { + delete croppedBitmap; + croppedBitmap = nullptr; + } + + if (newBitmap != nullptr) + { + GetImageCache().Put(m_Options, newBitmap); + handle = GetImageCache().Get(m_Options); + if (!handle) return; + } + } + + if (handle) + { + m_BitmapProcessed = handle; + } +} + +bool GeneralImage::CompareColorMatrix(const D2D1_MATRIX_5X4_F& a, const D2D1_MATRIX_5X4_F& b) +{ + for (int i = 0; i < 5; ++i) + { + for (int j = 0; j < 4; ++j) + { + if (a.m[i][j] != b.m[i][j]) + { + return false; + } + } + } + return true; +} diff --git a/Library/GeneralImage.h b/Library/GeneralImage.h index 5d49fe63a..61decd750 100644 --- a/Library/GeneralImage.h +++ b/Library/GeneralImage.h @@ -1,97 +1,97 @@ -/* Copyright (C) 2018 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#ifndef __GENERALIMAGE_H__ -#define __GENERALIMAGE_H__ - -#include "../Common/Gfx/D2DBitmap.h" -#include "../Common/Gfx/Util/D2DEffectStream.h" -#include -#include "Skin.h" -#include "ImageCache.h" -#include "ImageOptions.h" - -/* -** Helper macro to define an array of option names. A prefix must be given. -** -*/ -#define GeneralImageHelper_DefineOptionArray(name, prefix) \ - const WCHAR* (name)[GeneralImage::OptionCount] = { \ - prefix L"ImageCrop", \ - prefix L"Greyscale", \ - prefix L"ImageTint", \ - prefix L"ImageAlpha", \ - prefix L"ColorMatrix1", \ - prefix L"ColorMatrix2", \ - prefix L"ColorMatrix3", \ - prefix L"ColorMatrix4", \ - prefix L"ColorMatrix5", \ - prefix L"ImageFlip", \ - prefix L"ImageRotate", \ - prefix L"UseExifOrientation", \ - prefix L"ImagePath" \ - }; - -class GeneralImage -{ -public: - enum OptionIndex - { - OptionIndexImageCrop = 0, - OptionIndexGreyscale, - OptionIndexImageTint, - OptionIndexImageAlpha, - OptionIndexColorMatrix1, - OptionIndexColorMatrix2, - OptionIndexColorMatrix3, - OptionIndexColorMatrix4, - OptionIndexColorMatrix5, - OptionIndexImageFlip, - OptionIndexImageRotate, - OptionIndexUseExifOrientation, - OptionIndexImagePath, - - OptionCount - }; - - GeneralImage(const WCHAR* name = L"ImageName", const WCHAR** optionArray = c_DefaultOptionArray, - bool disableTransform = false, Skin* skin = nullptr); - ~GeneralImage(); - - void DisposeImage(); - - bool IsLoaded() { return m_BitmapProcessed != nullptr; } - Gfx::D2DBitmap* GetImage() { return m_BitmapProcessed ? m_BitmapProcessed->GetBitmap() : nullptr; } - - void ReadOptions(ConfigParser& parser, const WCHAR* section, const WCHAR* imagePath = L""); - bool LoadImage(const std::wstring& imageName); - -private: - - D2D1_SIZE_F ApplyCrop(Gfx::Util::D2DEffectStream* stream, Gfx::D2DBitmap* bitmap) const; - void ApplyTransforms(); - - ImageCacheHandle* m_Bitmap; - ImageCacheHandle* m_BitmapProcessed; - Skin* m_Skin; - - const WCHAR* m_Name; - const WCHAR** m_OptionArray; - const bool m_DisableTransform; - - ImageOptions m_Options; - - std::wstring m_Path; - - static bool CompareColorMatrix(const D2D1_MATRIX_5X4_F& a, const D2D1_MATRIX_5X4_F& b); - - static const D2D1_MATRIX_5X4_F c_GreyScaleMatrix; - static const D2D1_MATRIX_5X4_F c_IdentityMatrix; - static const WCHAR* c_DefaultOptionArray[OptionCount]; -}; - -#endif +/* Copyright (C) 2018 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#ifndef __GENERALIMAGE_H__ +#define __GENERALIMAGE_H__ + +#include "../Common/Gfx/D2DBitmap.h" +#include "../Common/Gfx/Util/D2DEffectStream.h" +#include +#include "Skin.h" +#include "ImageCache.h" +#include "ImageOptions.h" + +/* +** Helper macro to define an array of option names. A prefix must be given. +** +*/ +#define GeneralImageHelper_DefineOptionArray(name, prefix) \ + const WCHAR* (name)[GeneralImage::OptionCount] = { \ + prefix L"ImageCrop", \ + prefix L"Greyscale", \ + prefix L"ImageTint", \ + prefix L"ImageAlpha", \ + prefix L"ColorMatrix1", \ + prefix L"ColorMatrix2", \ + prefix L"ColorMatrix3", \ + prefix L"ColorMatrix4", \ + prefix L"ColorMatrix5", \ + prefix L"ImageFlip", \ + prefix L"ImageRotate", \ + prefix L"UseExifOrientation", \ + prefix L"ImagePath" \ + }; + +class GeneralImage +{ +public: + enum OptionIndex + { + OptionIndexImageCrop = 0, + OptionIndexGreyscale, + OptionIndexImageTint, + OptionIndexImageAlpha, + OptionIndexColorMatrix1, + OptionIndexColorMatrix2, + OptionIndexColorMatrix3, + OptionIndexColorMatrix4, + OptionIndexColorMatrix5, + OptionIndexImageFlip, + OptionIndexImageRotate, + OptionIndexUseExifOrientation, + OptionIndexImagePath, + + OptionCount + }; + + GeneralImage(const WCHAR* name = L"ImageName", const WCHAR** optionArray = c_DefaultOptionArray, + bool disableTransform = false, Skin* skin = nullptr); + ~GeneralImage(); + + void DisposeImage(); + + bool IsLoaded() { return m_BitmapProcessed != nullptr; } + Gfx::D2DBitmap* GetImage() { return m_BitmapProcessed ? m_BitmapProcessed->GetBitmap() : nullptr; } + + void ReadOptions(ConfigParser& parser, const WCHAR* section, const WCHAR* imagePath = L""); + bool LoadImage(const std::wstring& imageName); + +private: + + D2D1_SIZE_F ApplyCrop(Gfx::Util::D2DEffectStream* stream, Gfx::D2DBitmap* bitmap) const; + void ApplyTransforms(); + + ImageCacheHandle* m_Bitmap; + ImageCacheHandle* m_BitmapProcessed; + Skin* m_Skin; + + const WCHAR* m_Name; + const WCHAR** m_OptionArray; + const bool m_DisableTransform; + + ImageOptions m_Options; + + std::wstring m_Path; + + static bool CompareColorMatrix(const D2D1_MATRIX_5X4_F& a, const D2D1_MATRIX_5X4_F& b); + + static const D2D1_MATRIX_5X4_F c_GreyScaleMatrix; + static const D2D1_MATRIX_5X4_F c_IdentityMatrix; + static const WCHAR* c_DefaultOptionArray[OptionCount]; +}; + +#endif diff --git a/Library/ImageCache.cpp b/Library/ImageCache.cpp index 986241f38..c28c3c050 100644 --- a/Library/ImageCache.cpp +++ b/Library/ImageCache.cpp @@ -1,75 +1,75 @@ -/* Copyright (C) 2018 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#include "StdAfx.h" -#include "ImageCache.h" - -void ImageCache::Update(const ImageOptions& key, Gfx::D2DBitmap* item) -{ - if (m_Bitmap) - { - delete m_Bitmap; - m_Bitmap = nullptr; - } - - m_Key = key; - m_Bitmap = item; -} - -ImageCacheHandle::~ImageCacheHandle() -{ - --m_Cache->m_Instances; - if (m_Cache->m_Instances == 0) - { - m_Cache->m_Pool->Remove(m_Cache->m_Key); - } -} - -ImageCachePool::ImageCachePool() -{ -} - -ImageCachePool::~ImageCachePool() -{ -} - -ImageCachePool& ImageCachePool::GetInstance() -{ - static ImageCachePool s_CachePool; - return s_CachePool; -} - -ImageCacheHandle* ImageCachePool::Get(const ImageOptions& key) -{ - const auto find = m_CachePool.find(key); - if (find == m_CachePool.end()) return nullptr; - return new ImageCacheHandle(find->second); -} - -void ImageCachePool::Put(const ImageOptions& key, Gfx::D2DBitmap* item) -{ - if (m_CachePool.find(key) == m_CachePool.end()) - { - m_CachePool[key] = new ImageCache(key, item, this); - return; - } - - // sanity check - if (item != nullptr) - { - m_CachePool[key]->Update(key, item); - } -} - -void ImageCachePool::Remove(const ImageOptions& item) -{ - auto it = m_CachePool.find(item); - if (it == m_CachePool.end()) return; - - delete it->second; - m_CachePool.erase(it); -} +/* Copyright (C) 2018 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#include "StdAfx.h" +#include "ImageCache.h" + +void ImageCache::Update(const ImageOptions& key, Gfx::D2DBitmap* item) +{ + if (m_Bitmap) + { + delete m_Bitmap; + m_Bitmap = nullptr; + } + + m_Key = key; + m_Bitmap = item; +} + +ImageCacheHandle::~ImageCacheHandle() +{ + --m_Cache->m_Instances; + if (m_Cache->m_Instances == 0) + { + m_Cache->m_Pool->Remove(m_Cache->m_Key); + } +} + +ImageCachePool::ImageCachePool() +{ +} + +ImageCachePool::~ImageCachePool() +{ +} + +ImageCachePool& ImageCachePool::GetInstance() +{ + static ImageCachePool s_CachePool; + return s_CachePool; +} + +ImageCacheHandle* ImageCachePool::Get(const ImageOptions& key) +{ + const auto find = m_CachePool.find(key); + if (find == m_CachePool.end()) return nullptr; + return new ImageCacheHandle(find->second); +} + +void ImageCachePool::Put(const ImageOptions& key, Gfx::D2DBitmap* item) +{ + if (m_CachePool.find(key) == m_CachePool.end()) + { + m_CachePool[key] = new ImageCache(key, item, this); + return; + } + + // sanity check + if (item != nullptr) + { + m_CachePool[key]->Update(key, item); + } +} + +void ImageCachePool::Remove(const ImageOptions& item) +{ + auto it = m_CachePool.find(item); + if (it == m_CachePool.end()) return; + + delete it->second; + m_CachePool.erase(it); +} diff --git a/Library/ImageOptions.h b/Library/ImageOptions.h index 2c2384cae..e8007b67a 100644 --- a/Library/ImageOptions.h +++ b/Library/ImageOptions.h @@ -1,72 +1,72 @@ -/* Copyright (C) 2018 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#ifndef __IMAGEOPTIONS_H__ -#define __IMAGEOPTIONS_H__ - -#include "../Common/Gfx/D2DBitmap.h" -#include "../Common/Gfx/Util/D2DEffectStream.h" - -struct ImageOptions : Gfx::FileInfo -{ - ImageOptions() : - m_ColorMatrix(D2D1::Matrix5x4F( - -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, - -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, - -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, - -1.0f, -1.0f, -1.0f, -1.0f, -1.0f)), - m_Crop(D2D1::RectF(-1.0f, -1.0f, -1.0f, -1.0f)), - m_CropMode(CROPMODE_TL), - m_GreyScale(false), - m_Rotate(0.0f), - m_Flip(Gfx::Util::FlipType::None), - m_UseExifOrientation(false) - {} - - enum CROPMODE - { - CROPMODE_TL = 1, - CROPMODE_TR, - CROPMODE_BR, - CROPMODE_BL, - CROPMODE_C - }; - - bool operator==(const ImageOptions& other) const - { - for (int i = 0; i < 5; ++i) - { - for (int j = 0; j < 4; ++j) - { - if (m_ColorMatrix.m[i][j] != other.m_ColorMatrix.m[i][j]) return false; - } - } - - return wcscmp(m_Path.c_str(), other.m_Path.c_str()) == 0 && - m_FileSize == other.m_FileSize && - m_FileTime == other.m_FileTime && - m_Rotate == other.m_Rotate && - m_GreyScale == other.m_GreyScale && - m_UseExifOrientation == other.m_UseExifOrientation && - m_Flip == other.m_Flip && - m_CropMode == other.m_CropMode && - m_Crop.left == other.m_Crop.left && - m_Crop.top == other.m_Crop.top && - m_Crop.right == other.m_Crop.right && - m_Crop.bottom == other.m_Crop.bottom; - } - - D2D1_MATRIX_5X4_F m_ColorMatrix; - D2D1_RECT_F m_Crop; - CROPMODE m_CropMode; - bool m_GreyScale; - FLOAT m_Rotate; - Gfx::Util::FlipType m_Flip; - bool m_UseExifOrientation; -}; - -#endif +/* Copyright (C) 2018 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#ifndef __IMAGEOPTIONS_H__ +#define __IMAGEOPTIONS_H__ + +#include "../Common/Gfx/D2DBitmap.h" +#include "../Common/Gfx/Util/D2DEffectStream.h" + +struct ImageOptions : Gfx::FileInfo +{ + ImageOptions() : + m_ColorMatrix(D2D1::Matrix5x4F( + -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, -1.0f)), + m_Crop(D2D1::RectF(-1.0f, -1.0f, -1.0f, -1.0f)), + m_CropMode(CROPMODE_TL), + m_GreyScale(false), + m_Rotate(0.0f), + m_Flip(Gfx::Util::FlipType::None), + m_UseExifOrientation(false) + {} + + enum CROPMODE + { + CROPMODE_TL = 1, + CROPMODE_TR, + CROPMODE_BR, + CROPMODE_BL, + CROPMODE_C + }; + + bool operator==(const ImageOptions& other) const + { + for (int i = 0; i < 5; ++i) + { + for (int j = 0; j < 4; ++j) + { + if (m_ColorMatrix.m[i][j] != other.m_ColorMatrix.m[i][j]) return false; + } + } + + return wcscmp(m_Path.c_str(), other.m_Path.c_str()) == 0 && + m_FileSize == other.m_FileSize && + m_FileTime == other.m_FileTime && + m_Rotate == other.m_Rotate && + m_GreyScale == other.m_GreyScale && + m_UseExifOrientation == other.m_UseExifOrientation && + m_Flip == other.m_Flip && + m_CropMode == other.m_CropMode && + m_Crop.left == other.m_Crop.left && + m_Crop.top == other.m_Crop.top && + m_Crop.right == other.m_Crop.right && + m_Crop.bottom == other.m_Crop.bottom; + } + + D2D1_MATRIX_5X4_F m_ColorMatrix; + D2D1_RECT_F m_Crop; + CROPMODE m_CropMode; + bool m_GreyScale; + FLOAT m_Rotate; + Gfx::Util::FlipType m_Flip; + bool m_UseExifOrientation; +}; + +#endif diff --git a/Library/MeasureMediaKey.h b/Library/MeasureMediaKey.h index 69b4fefcb..50d25e3de 100644 --- a/Library/MeasureMediaKey.h +++ b/Library/MeasureMediaKey.h @@ -1,29 +1,29 @@ -/* Copyright (C) 2015 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#ifndef RM_LIBRARY_MEASUREMEDIAKEY_H_ -#define RM_LIBRARY_MEASUREMEDIAKEY_H_ - -#include "Measure.h" - -class MeasureMediaKey : public Measure -{ -public: - MeasureMediaKey(Skin* skin, const WCHAR* name); - virtual ~MeasureMediaKey(); - - MeasureMediaKey(const MeasureMediaKey& other) = delete; - MeasureMediaKey& operator=(MeasureMediaKey other) = delete; - - UINT GetTypeID() override { return TypeID(); } - -protected: - void UpdateValue() override {}; - void Command(const std::wstring& command) override; -}; - -#endif +/* Copyright (C) 2015 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#ifndef RM_LIBRARY_MEASUREMEDIAKEY_H_ +#define RM_LIBRARY_MEASUREMEDIAKEY_H_ + +#include "Measure.h" + +class MeasureMediaKey : public Measure +{ +public: + MeasureMediaKey(Skin* skin, const WCHAR* name); + virtual ~MeasureMediaKey(); + + MeasureMediaKey(const MeasureMediaKey& other) = delete; + MeasureMediaKey& operator=(MeasureMediaKey other) = delete; + + UINT GetTypeID() override { return TypeID(); } + +protected: + void UpdateValue() override {}; + void Command(const std::wstring& command) override; +}; + +#endif diff --git a/Library/MeasureNowPlaying.cpp b/Library/MeasureNowPlaying.cpp index 64958bc99..4044f7213 100644 --- a/Library/MeasureNowPlaying.cpp +++ b/Library/MeasureNowPlaying.cpp @@ -1,565 +1,565 @@ -/* Copyright (C) 2011 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#include "StdAfx.h" -#include "MeasureNowPlaying.h" -#include "Rainmeter.h" -#include "NowPlaying/Internet.h" -#include "NowPlaying/PlayerAIMP.h" -#include "NowPlaying/PlayerCAD.h" -#include "NowPlaying/PlayerITunes.h" -#include "NowPlaying/PlayerSpotify.h" -#include "NowPlaying/PlayerWinamp.h" -#include "NowPlaying/PlayerWLM.h" -#include "NowPlaying/PlayerWMP.h" - -struct ParentMeasure -{ - ParentMeasure() : - player(), - owner(), - measureCount(1), - trackCount(0), - disableLeadingZero(false) - {} - - Player* player; - MeasureNowPlaying* owner; - std::wstring trackChangeAction; - std::wstring playerPath; - UINT measureCount; - UINT trackCount; - bool disableLeadingZero; -}; - -static std::vector g_ParentMeasures; -bool g_Initialized = false; -HINSTANCE g_Instance = nullptr; - -MeasureNowPlaying::MeasureNowPlaying(Skin* skin, const WCHAR* name) : Measure(skin, name), - m_Type(MEASURE_NONE), - m_Parent() -{ - if (!g_Initialized) - { - Internet::Initialize(); - g_Initialized = true; - } -} - -MeasureNowPlaying::~MeasureNowPlaying() -{ - if (m_Parent) - { - Player* player = m_Parent->player; - if (--m_Parent->measureCount == 0) - { - player->RemoveInstance(); - delete m_Parent; - - auto iter = std::find(g_ParentMeasures.begin(), g_ParentMeasures.end(), m_Parent); - g_ParentMeasures.erase(iter); - - if (g_ParentMeasures.empty()) - { - Internet::Finalize(); - g_Initialized = false; - } - } - } -} - -void MeasureNowPlaying::ReadOptions(ConfigParser& parser, const WCHAR* section) -{ - Measure::ReadOptions(parser, section); - - // Data is stored in two structs: Measure and ParentMeasure. ParentMeasure is created for measures - // with PlayerName=someplayer. Measure is created for all measures and points to ParentMeasure as - // referenced in PlayerName=[section]. - - // Read settings from the ini-file - LPCWSTR str = parser.ReadString(section, L"PlayerName", L"", false).c_str(); - if (str[0] == L'[') - { - if (m_Parent) - { - // Don't let a measure measure change its parent - } - else - { - // PlayerName starts with [ so use referenced section - ++str; - size_t len = wcslen(str); - if (len > 0 && str[len - 1] == L']') - { - --len; - - std::vector::iterator iter = g_ParentMeasures.begin(); - for ( ; iter != g_ParentMeasures.end(); ++iter) - { - if (GetSkin() == (*iter)->owner->GetSkin() && - _wcsnicmp(str, (*iter)->owner->GetName(), len) == 0) - { - // Use same ParentMeasure as referenced section - m_Parent = (*iter); - ++m_Parent->measureCount; - - break; - } - } - - if (!m_Parent) - { - // The referenced section doesn't exist - LogWarningF(this, L"Invalid PlayerName=%s", str - 1); - return; - } - } - } - } - else - { - // ParentMeasure is created when PlayerName is an actual player (and not a reference) - Player* oldPlayer = nullptr; - if (m_Parent) - { - if (m_Parent->owner != this) - { - // Don't let a measure-only measure become a parent measure - return; - } - - oldPlayer = m_Parent->player; - } - else - { - m_Parent = new ParentMeasure; - g_ParentMeasures.push_back(m_Parent); - m_Parent->owner = this; - } - - if (_wcsicmp(L"AIMP", str) == 0) - { - m_Parent->player = PlayerAIMP::Create(); - } - else if (_wcsicmp(L"CAD", str) == 0) - { - m_Parent->player = PlayerCAD::Create(); - } - else if (_wcsicmp(L"foobar2000", str) == 0) - { - HWND fooWindow = FindWindow(L"foo_rainmeter_class", nullptr); - if (fooWindow) - { - const WCHAR* error = L"Your foobar2000 plugin is out of date.\n\nDo you want to update the plugin now?"; - if (MessageBox(nullptr, error, L"Rainmeter", MB_YESNO | MB_ICONINFORMATION | MB_TOPMOST) == IDYES) - { - ShellExecute(nullptr, L"open", L"http://github.com/poiru/foo-cad#readme", nullptr, nullptr, SW_SHOWNORMAL); - } - } - - m_Parent->player = PlayerCAD::Create(); - } - else if (_wcsicmp(L"iTunes", str) == 0) - { - m_Parent->player = PlayerITunes::Create(); - } - else if (_wcsicmp(L"MediaMonkey", str) == 0) - { - m_Parent->player = PlayerWinamp::Create(WA_MEDIAMONKEY); - } - else if (_wcsicmp(L"Spotify", str) == 0) - { - m_Parent->player = PlayerSpotify::Create(); - } - else if (_wcsicmp(L"WinAmp", str) == 0) - { - m_Parent->player = PlayerWinamp::Create(WA_WINAMP); - } - else if (_wcsicmp(L"WMP", str) == 0) - { - m_Parent->player = PlayerWMP::Create(); - } - else - { - // Default to WLM - m_Parent->player = PlayerWLM::Create(); - - if (_wcsicmp(L"WLM", str) != 0) - { - LogErrorF(this, L"Invalid PlayerName=%s", str); - } - } - - m_Parent->player->AddInstance(); - m_Parent->playerPath = parser.ReadString(section, L"PlayerPath", L""); - m_Parent->trackChangeAction = parser.ReadString(section, L"TrackChangeAction", L"", false); - m_Parent->disableLeadingZero = parser.ReadBool(section, L"DisableLeadingZero", false); - - if (oldPlayer) - { - m_Parent->player->SetMeasures(oldPlayer->GetMeasures()); - - // Remove instance here so that player doesn't have to reinitialize if PlayerName was - // not changed. - oldPlayer->RemoveInstance(); - } - } - - str = parser.ReadString(section, L"PlayerType", L"").c_str(); - if (_wcsicmp(L"ARTIST", str) == 0) - { - m_Type = MEASURE_ARTIST; - } - else if (_wcsicmp(L"TITLE", str) == 0) - { - m_Type = MEASURE_TITLE; - } - else if (_wcsicmp(L"ALBUM", str) == 0) - { - m_Type = MEASURE_ALBUM; - } - else if (_wcsicmp(L"COVER", str) == 0) - { - m_Type = MEASURE_COVER; - } - else if (_wcsicmp(L"DURATION", str) == 0) - { - m_Type = MEASURE_DURATION; - } - else if (_wcsicmp(L"POSITION", str) == 0) - { - m_Type = MEASURE_POSITION; - } - else if (_wcsicmp(L"PROGRESS", str) == 0) - { - m_Type = MEASURE_PROGRESS; - m_MaxValue = 100.0; - } - else if (_wcsicmp(L"RATING", str) == 0) - { - m_Type = MEASURE_RATING; - m_MaxValue = 5.0; - } - else if (_wcsicmp(L"STATE", str) == 0) - { - m_Type = MEASURE_STATE; - } - else if (_wcsicmp(L"STATUS", str) == 0) - { - m_Type = MEASURE_STATUS; - } - else if (_wcsicmp(L"VOLUME", str) == 0) - { - m_Type = MEASURE_VOLUME; - m_MaxValue = 100.0; - } - else if (_wcsicmp(L"SHUFFLE", str) == 0) - { - m_Type = MEASURE_SHUFFLE; - } - else if (_wcsicmp(L"REPEAT", str) == 0) - { - m_Type = MEASURE_REPEAT; - } - else if (_wcsicmp(L"LYRICS", str) == 0) - { - //LogWarningF(this, L"Using undocumented PlayerType=LYRICS!"); - m_Type = MEASURE_LYRICS; - } - else if (_wcsicmp(L"FILE", str) == 0) - { - m_Type = MEASURE_FILE; - } - else if (_wcsicmp(L"NUMBER", str) == 0) - { - m_Type = MEASURE_NUMBER; - } - else if (_wcsicmp(L"YEAR", str) == 0) - { - m_Type = MEASURE_YEAR; - } - else if (_wcsicmp(L"GENRE", str) == 0) - { - m_Type = MEASURE_GENRE; - } - else - { - LogErrorF(this, L"Invalid PlayerType=%s", str); - } - - m_Parent->player->AddMeasure(m_Type); -} - -void MeasureNowPlaying::UpdateValue() -{ - m_Value = 0.0; - if (!m_Parent) - { - return; - } - - Player* player = m_Parent->player; - - // Only allow parent measure to update - if (m_Parent->owner == this) - { - player->UpdateMeasure(); - - // Execute TrackChangeAction= if necessary - if (!m_Parent->trackChangeAction.empty() && - m_Parent->trackCount != player->GetTrackCount()) - { - GetRainmeter().DelayedExecuteCommand(m_Parent->trackChangeAction.c_str(), GetSkin()); - m_Parent->trackCount = player->GetTrackCount(); - } - } - - switch (m_Type) - { - case MEASURE_DURATION: - m_Value = player->GetDuration(); - break; - case MEASURE_POSITION: - m_Value = player->GetPosition(); - break; - case MEASURE_PROGRESS: - if (player->GetDuration()) - { - m_Value = ((double)player->GetPosition() * 100.0) / player->GetDuration(); - } - break; - case MEASURE_RATING: - m_Value = player->GetRating(); - break; - case MEASURE_VOLUME: - m_Value = player->GetVolume(); - break; - case MEASURE_STATE: - m_Value = player->GetState(); - break; - case MEASURE_STATUS: - m_Value = player->IsInitialized(); - break; - case MEASURE_SHUFFLE: - m_Value = player->GetShuffle(); - break; - case MEASURE_REPEAT: - m_Value = player->GetRepeat(); - break; - case MEASURE_NUMBER: - m_Value = player->GetNumber(); - break; - case MEASURE_YEAR: - m_Value = player->GetYear(); - break; - } -} - -const WCHAR* MeasureNowPlaying::GetStringValue() -{ - if (!m_Parent) return nullptr; - - const Player* player = m_Parent->player; - static WCHAR buffer[32]; - const WCHAR* str = nullptr; - switch (m_Type) - { - case MEASURE_ARTIST: - str = player->GetArtist(); - break; - - case MEASURE_TITLE: - str = player->GetTitle(); - break; - - case MEASURE_ALBUM: - str = player->GetAlbum(); - break; - - case MEASURE_LYRICS: - str = player->GetLyrics(); - break; - - case MEASURE_COVER: - str = player->GetCoverPath(); - break; - - case MEASURE_FILE: - str = player->GetFilePath(); - break; - - case MEASURE_DURATION: - SecondsToTime(player->GetDuration(), m_Parent->disableLeadingZero, buffer); - str = buffer; - break; - - case MEASURE_POSITION: - SecondsToTime(player->GetPosition(), m_Parent->disableLeadingZero, buffer); - str = buffer; - break; - - case MEASURE_GENRE: - str = player->GetGenre(); - break; - } - - return str ? CheckSubstitute(str) : nullptr; -} - -void MeasureNowPlaying::Command(const std::wstring& command) -{ - const WCHAR* args = command.c_str(); - - if (!m_Parent) return; - - Player* player = m_Parent->player; - - if (!player->IsInitialized()) - { - if (_wcsicmp(args, L"OpenPlayer") == 0 || _wcsicmp(args, L"TogglePlayer") == 0) - { - player->OpenPlayer(m_Parent->playerPath); - } - } - else if (_wcsicmp(args, L"Pause") == 0) - { - player->Pause(); - } - else if (_wcsicmp(args, L"Play") == 0) - { - player->Play(); - } - else if (_wcsicmp(args, L"PlayPause") == 0) - { - (player->GetState() != STATE_PLAYING) ? player->Play() : player->Pause(); - } - else if (_wcsicmp(args, L"Next") == 0) - { - player->Next(); - } - else if (_wcsicmp(args, L"Previous") == 0) - { - player->Previous(); - } - else if (_wcsicmp(args, L"Stop") == 0) - { - player->Stop(); - } - else if (_wcsicmp(args, L"OpenPlayer") == 0) - { - player->OpenPlayer(m_Parent->playerPath); - } - else if (_wcsicmp(args, L"ClosePlayer") == 0 || _wcsicmp(args, L"TogglePlayer") == 0) - { - player->ClosePlayer(); - } - else - { - LPCWSTR arg = wcschr(args, L' '); - - if (arg) - { - ++arg; // Skip the space - - if (_wcsnicmp(args, L"SetPosition", 11) == 0) - { - int position = (int)(_wtof(arg) * (double)player->GetDuration()) / 100; - if (arg[0] == L'+' || arg[0] == L'-') - { - position += player->GetPosition(); - } - - player->SetPosition(position); - } - else if (_wcsnicmp(args, L"SetRating", 9) == 0) - { - int rating = _wtoi(arg); - if (rating >= 0 && rating <= 5) - { - player->SetRating(rating); - } - } - else if (_wcsnicmp(args, L"SetVolume", 9) == 0) - { - int volume = _wtoi(arg); - if (arg[0] == L'+' || arg[0] == L'-') - { - // Relative to current volume - volume += player->GetVolume(); - } - - if (volume < 0) - { - volume = 0; - } - else if (volume > 100) - { - volume = 100; - } - player->SetVolume(volume);; - } - else if (_wcsnicmp(args, L"SetShuffle", 9) == 0) - { - int state = _wtoi(arg); - if (state == -1) - { - player->SetShuffle(!player->GetShuffle()); - } - else if (state == 0 || state == 1) - { - player->SetShuffle(state != 0); - } - } - else if (_wcsnicmp(args, L"SetRepeat", 9) == 0) - { - int state = _wtoi(arg); - if (state == -1) - { - player->SetRepeat(!player->GetRepeat()); - } - else if (state == 0 || state == 1) - { - player->SetRepeat(state != 0); - } - } - else - { - LogWarningF(this, L"Invalid !CommandMeasure"); - } - } - else - { - LogWarningF(this, L"Invalid !CommandMeasure"); - } - } -} - -void SecondsToTime(UINT seconds, bool leadingZero, WCHAR* buffer) -{ - int hours = seconds; - int mins = seconds; - hours /= 3600; - mins %= 3600; - int secs = mins; - mins /= 60; - secs %= 60; - - if (seconds < 0) - { - hours = mins = secs = 0; - } - - if (hours) - { - _snwprintf_s(buffer, 32, _TRUNCATE, leadingZero ? L"%i:%02i:%02i" : L"%02i:%02i:%02i", hours, mins, secs); - } - else - { - _snwprintf_s(buffer, 32, _TRUNCATE, leadingZero ? L"%i:%02i" : L"%02i:%02i", mins, secs); - } -} +/* Copyright (C) 2011 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#include "StdAfx.h" +#include "MeasureNowPlaying.h" +#include "Rainmeter.h" +#include "NowPlaying/Internet.h" +#include "NowPlaying/PlayerAIMP.h" +#include "NowPlaying/PlayerCAD.h" +#include "NowPlaying/PlayerITunes.h" +#include "NowPlaying/PlayerSpotify.h" +#include "NowPlaying/PlayerWinamp.h" +#include "NowPlaying/PlayerWLM.h" +#include "NowPlaying/PlayerWMP.h" + +struct ParentMeasure +{ + ParentMeasure() : + player(), + owner(), + measureCount(1), + trackCount(0), + disableLeadingZero(false) + {} + + Player* player; + MeasureNowPlaying* owner; + std::wstring trackChangeAction; + std::wstring playerPath; + UINT measureCount; + UINT trackCount; + bool disableLeadingZero; +}; + +static std::vector g_ParentMeasures; +bool g_Initialized = false; +HINSTANCE g_Instance = nullptr; + +MeasureNowPlaying::MeasureNowPlaying(Skin* skin, const WCHAR* name) : Measure(skin, name), + m_Type(MEASURE_NONE), + m_Parent() +{ + if (!g_Initialized) + { + Internet::Initialize(); + g_Initialized = true; + } +} + +MeasureNowPlaying::~MeasureNowPlaying() +{ + if (m_Parent) + { + Player* player = m_Parent->player; + if (--m_Parent->measureCount == 0) + { + player->RemoveInstance(); + delete m_Parent; + + auto iter = std::find(g_ParentMeasures.begin(), g_ParentMeasures.end(), m_Parent); + g_ParentMeasures.erase(iter); + + if (g_ParentMeasures.empty()) + { + Internet::Finalize(); + g_Initialized = false; + } + } + } +} + +void MeasureNowPlaying::ReadOptions(ConfigParser& parser, const WCHAR* section) +{ + Measure::ReadOptions(parser, section); + + // Data is stored in two structs: Measure and ParentMeasure. ParentMeasure is created for measures + // with PlayerName=someplayer. Measure is created for all measures and points to ParentMeasure as + // referenced in PlayerName=[section]. + + // Read settings from the ini-file + LPCWSTR str = parser.ReadString(section, L"PlayerName", L"", false).c_str(); + if (str[0] == L'[') + { + if (m_Parent) + { + // Don't let a measure measure change its parent + } + else + { + // PlayerName starts with [ so use referenced section + ++str; + size_t len = wcslen(str); + if (len > 0 && str[len - 1] == L']') + { + --len; + + std::vector::iterator iter = g_ParentMeasures.begin(); + for ( ; iter != g_ParentMeasures.end(); ++iter) + { + if (GetSkin() == (*iter)->owner->GetSkin() && + _wcsnicmp(str, (*iter)->owner->GetName(), len) == 0) + { + // Use same ParentMeasure as referenced section + m_Parent = (*iter); + ++m_Parent->measureCount; + + break; + } + } + + if (!m_Parent) + { + // The referenced section doesn't exist + LogWarningF(this, L"Invalid PlayerName=%s", str - 1); + return; + } + } + } + } + else + { + // ParentMeasure is created when PlayerName is an actual player (and not a reference) + Player* oldPlayer = nullptr; + if (m_Parent) + { + if (m_Parent->owner != this) + { + // Don't let a measure-only measure become a parent measure + return; + } + + oldPlayer = m_Parent->player; + } + else + { + m_Parent = new ParentMeasure; + g_ParentMeasures.push_back(m_Parent); + m_Parent->owner = this; + } + + if (_wcsicmp(L"AIMP", str) == 0) + { + m_Parent->player = PlayerAIMP::Create(); + } + else if (_wcsicmp(L"CAD", str) == 0) + { + m_Parent->player = PlayerCAD::Create(); + } + else if (_wcsicmp(L"foobar2000", str) == 0) + { + HWND fooWindow = FindWindow(L"foo_rainmeter_class", nullptr); + if (fooWindow) + { + const WCHAR* error = L"Your foobar2000 plugin is out of date.\n\nDo you want to update the plugin now?"; + if (MessageBox(nullptr, error, L"Rainmeter", MB_YESNO | MB_ICONINFORMATION | MB_TOPMOST) == IDYES) + { + ShellExecute(nullptr, L"open", L"http://github.com/poiru/foo-cad#readme", nullptr, nullptr, SW_SHOWNORMAL); + } + } + + m_Parent->player = PlayerCAD::Create(); + } + else if (_wcsicmp(L"iTunes", str) == 0) + { + m_Parent->player = PlayerITunes::Create(); + } + else if (_wcsicmp(L"MediaMonkey", str) == 0) + { + m_Parent->player = PlayerWinamp::Create(WA_MEDIAMONKEY); + } + else if (_wcsicmp(L"Spotify", str) == 0) + { + m_Parent->player = PlayerSpotify::Create(); + } + else if (_wcsicmp(L"WinAmp", str) == 0) + { + m_Parent->player = PlayerWinamp::Create(WA_WINAMP); + } + else if (_wcsicmp(L"WMP", str) == 0) + { + m_Parent->player = PlayerWMP::Create(); + } + else + { + // Default to WLM + m_Parent->player = PlayerWLM::Create(); + + if (_wcsicmp(L"WLM", str) != 0) + { + LogErrorF(this, L"Invalid PlayerName=%s", str); + } + } + + m_Parent->player->AddInstance(); + m_Parent->playerPath = parser.ReadString(section, L"PlayerPath", L""); + m_Parent->trackChangeAction = parser.ReadString(section, L"TrackChangeAction", L"", false); + m_Parent->disableLeadingZero = parser.ReadBool(section, L"DisableLeadingZero", false); + + if (oldPlayer) + { + m_Parent->player->SetMeasures(oldPlayer->GetMeasures()); + + // Remove instance here so that player doesn't have to reinitialize if PlayerName was + // not changed. + oldPlayer->RemoveInstance(); + } + } + + str = parser.ReadString(section, L"PlayerType", L"").c_str(); + if (_wcsicmp(L"ARTIST", str) == 0) + { + m_Type = MEASURE_ARTIST; + } + else if (_wcsicmp(L"TITLE", str) == 0) + { + m_Type = MEASURE_TITLE; + } + else if (_wcsicmp(L"ALBUM", str) == 0) + { + m_Type = MEASURE_ALBUM; + } + else if (_wcsicmp(L"COVER", str) == 0) + { + m_Type = MEASURE_COVER; + } + else if (_wcsicmp(L"DURATION", str) == 0) + { + m_Type = MEASURE_DURATION; + } + else if (_wcsicmp(L"POSITION", str) == 0) + { + m_Type = MEASURE_POSITION; + } + else if (_wcsicmp(L"PROGRESS", str) == 0) + { + m_Type = MEASURE_PROGRESS; + m_MaxValue = 100.0; + } + else if (_wcsicmp(L"RATING", str) == 0) + { + m_Type = MEASURE_RATING; + m_MaxValue = 5.0; + } + else if (_wcsicmp(L"STATE", str) == 0) + { + m_Type = MEASURE_STATE; + } + else if (_wcsicmp(L"STATUS", str) == 0) + { + m_Type = MEASURE_STATUS; + } + else if (_wcsicmp(L"VOLUME", str) == 0) + { + m_Type = MEASURE_VOLUME; + m_MaxValue = 100.0; + } + else if (_wcsicmp(L"SHUFFLE", str) == 0) + { + m_Type = MEASURE_SHUFFLE; + } + else if (_wcsicmp(L"REPEAT", str) == 0) + { + m_Type = MEASURE_REPEAT; + } + else if (_wcsicmp(L"LYRICS", str) == 0) + { + //LogWarningF(this, L"Using undocumented PlayerType=LYRICS!"); + m_Type = MEASURE_LYRICS; + } + else if (_wcsicmp(L"FILE", str) == 0) + { + m_Type = MEASURE_FILE; + } + else if (_wcsicmp(L"NUMBER", str) == 0) + { + m_Type = MEASURE_NUMBER; + } + else if (_wcsicmp(L"YEAR", str) == 0) + { + m_Type = MEASURE_YEAR; + } + else if (_wcsicmp(L"GENRE", str) == 0) + { + m_Type = MEASURE_GENRE; + } + else + { + LogErrorF(this, L"Invalid PlayerType=%s", str); + } + + m_Parent->player->AddMeasure(m_Type); +} + +void MeasureNowPlaying::UpdateValue() +{ + m_Value = 0.0; + if (!m_Parent) + { + return; + } + + Player* player = m_Parent->player; + + // Only allow parent measure to update + if (m_Parent->owner == this) + { + player->UpdateMeasure(); + + // Execute TrackChangeAction= if necessary + if (!m_Parent->trackChangeAction.empty() && + m_Parent->trackCount != player->GetTrackCount()) + { + GetRainmeter().DelayedExecuteCommand(m_Parent->trackChangeAction.c_str(), GetSkin()); + m_Parent->trackCount = player->GetTrackCount(); + } + } + + switch (m_Type) + { + case MEASURE_DURATION: + m_Value = player->GetDuration(); + break; + case MEASURE_POSITION: + m_Value = player->GetPosition(); + break; + case MEASURE_PROGRESS: + if (player->GetDuration()) + { + m_Value = ((double)player->GetPosition() * 100.0) / player->GetDuration(); + } + break; + case MEASURE_RATING: + m_Value = player->GetRating(); + break; + case MEASURE_VOLUME: + m_Value = player->GetVolume(); + break; + case MEASURE_STATE: + m_Value = player->GetState(); + break; + case MEASURE_STATUS: + m_Value = player->IsInitialized(); + break; + case MEASURE_SHUFFLE: + m_Value = player->GetShuffle(); + break; + case MEASURE_REPEAT: + m_Value = player->GetRepeat(); + break; + case MEASURE_NUMBER: + m_Value = player->GetNumber(); + break; + case MEASURE_YEAR: + m_Value = player->GetYear(); + break; + } +} + +const WCHAR* MeasureNowPlaying::GetStringValue() +{ + if (!m_Parent) return nullptr; + + const Player* player = m_Parent->player; + static WCHAR buffer[32]; + const WCHAR* str = nullptr; + switch (m_Type) + { + case MEASURE_ARTIST: + str = player->GetArtist(); + break; + + case MEASURE_TITLE: + str = player->GetTitle(); + break; + + case MEASURE_ALBUM: + str = player->GetAlbum(); + break; + + case MEASURE_LYRICS: + str = player->GetLyrics(); + break; + + case MEASURE_COVER: + str = player->GetCoverPath(); + break; + + case MEASURE_FILE: + str = player->GetFilePath(); + break; + + case MEASURE_DURATION: + SecondsToTime(player->GetDuration(), m_Parent->disableLeadingZero, buffer); + str = buffer; + break; + + case MEASURE_POSITION: + SecondsToTime(player->GetPosition(), m_Parent->disableLeadingZero, buffer); + str = buffer; + break; + + case MEASURE_GENRE: + str = player->GetGenre(); + break; + } + + return str ? CheckSubstitute(str) : nullptr; +} + +void MeasureNowPlaying::Command(const std::wstring& command) +{ + const WCHAR* args = command.c_str(); + + if (!m_Parent) return; + + Player* player = m_Parent->player; + + if (!player->IsInitialized()) + { + if (_wcsicmp(args, L"OpenPlayer") == 0 || _wcsicmp(args, L"TogglePlayer") == 0) + { + player->OpenPlayer(m_Parent->playerPath); + } + } + else if (_wcsicmp(args, L"Pause") == 0) + { + player->Pause(); + } + else if (_wcsicmp(args, L"Play") == 0) + { + player->Play(); + } + else if (_wcsicmp(args, L"PlayPause") == 0) + { + (player->GetState() != STATE_PLAYING) ? player->Play() : player->Pause(); + } + else if (_wcsicmp(args, L"Next") == 0) + { + player->Next(); + } + else if (_wcsicmp(args, L"Previous") == 0) + { + player->Previous(); + } + else if (_wcsicmp(args, L"Stop") == 0) + { + player->Stop(); + } + else if (_wcsicmp(args, L"OpenPlayer") == 0) + { + player->OpenPlayer(m_Parent->playerPath); + } + else if (_wcsicmp(args, L"ClosePlayer") == 0 || _wcsicmp(args, L"TogglePlayer") == 0) + { + player->ClosePlayer(); + } + else + { + LPCWSTR arg = wcschr(args, L' '); + + if (arg) + { + ++arg; // Skip the space + + if (_wcsnicmp(args, L"SetPosition", 11) == 0) + { + int position = (int)(_wtof(arg) * (double)player->GetDuration()) / 100; + if (arg[0] == L'+' || arg[0] == L'-') + { + position += player->GetPosition(); + } + + player->SetPosition(position); + } + else if (_wcsnicmp(args, L"SetRating", 9) == 0) + { + int rating = _wtoi(arg); + if (rating >= 0 && rating <= 5) + { + player->SetRating(rating); + } + } + else if (_wcsnicmp(args, L"SetVolume", 9) == 0) + { + int volume = _wtoi(arg); + if (arg[0] == L'+' || arg[0] == L'-') + { + // Relative to current volume + volume += player->GetVolume(); + } + + if (volume < 0) + { + volume = 0; + } + else if (volume > 100) + { + volume = 100; + } + player->SetVolume(volume);; + } + else if (_wcsnicmp(args, L"SetShuffle", 9) == 0) + { + int state = _wtoi(arg); + if (state == -1) + { + player->SetShuffle(!player->GetShuffle()); + } + else if (state == 0 || state == 1) + { + player->SetShuffle(state != 0); + } + } + else if (_wcsnicmp(args, L"SetRepeat", 9) == 0) + { + int state = _wtoi(arg); + if (state == -1) + { + player->SetRepeat(!player->GetRepeat()); + } + else if (state == 0 || state == 1) + { + player->SetRepeat(state != 0); + } + } + else + { + LogWarningF(this, L"Invalid !CommandMeasure"); + } + } + else + { + LogWarningF(this, L"Invalid !CommandMeasure"); + } + } +} + +void SecondsToTime(UINT seconds, bool leadingZero, WCHAR* buffer) +{ + int hours = seconds; + int mins = seconds; + hours /= 3600; + mins %= 3600; + int secs = mins; + mins /= 60; + secs %= 60; + + if (seconds < 0) + { + hours = mins = secs = 0; + } + + if (hours) + { + _snwprintf_s(buffer, 32, _TRUNCATE, leadingZero ? L"%i:%02i:%02i" : L"%02i:%02i:%02i", hours, mins, secs); + } + else + { + _snwprintf_s(buffer, 32, _TRUNCATE, leadingZero ? L"%i:%02i" : L"%02i:%02i", mins, secs); + } +} diff --git a/Library/MeasureNowPlaying.h b/Library/MeasureNowPlaying.h index b71f17ca8..63bf67895 100644 --- a/Library/MeasureNowPlaying.h +++ b/Library/MeasureNowPlaying.h @@ -1,43 +1,43 @@ -/* Copyright (C) 2011 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#ifndef RM_LIBRARY_MEASURENOWPLAYING_ -#define RM_LIBRARY_MEASURENOWPLAYING_ - -#include "Measure.h" - -enum MeasureType; -struct ParentMeasure; -class Player; - -class MeasureNowPlaying : public Measure -{ -public: - MeasureNowPlaying(Skin* skin, const WCHAR* name); - virtual ~MeasureNowPlaying(); - - MeasureNowPlaying(const MeasureNowPlaying& other) = delete; - MeasureNowPlaying& operator=(MeasureNowPlaying other) = delete; - - UINT GetTypeID() override { return TypeID(); } - - const WCHAR* GetStringValue() override; - - void Command(const std::wstring& command) override; - -protected: - void ReadOptions(ConfigParser& parser, const WCHAR* section) override; - void UpdateValue() override; - -private: - ParentMeasure* m_Parent; - MeasureType m_Type; -}; - -void SecondsToTime(UINT seconds, bool leadingZero, WCHAR* buffer); - -#endif +/* Copyright (C) 2011 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#ifndef RM_LIBRARY_MEASURENOWPLAYING_ +#define RM_LIBRARY_MEASURENOWPLAYING_ + +#include "Measure.h" + +enum MeasureType; +struct ParentMeasure; +class Player; + +class MeasureNowPlaying : public Measure +{ +public: + MeasureNowPlaying(Skin* skin, const WCHAR* name); + virtual ~MeasureNowPlaying(); + + MeasureNowPlaying(const MeasureNowPlaying& other) = delete; + MeasureNowPlaying& operator=(MeasureNowPlaying other) = delete; + + UINT GetTypeID() override { return TypeID(); } + + const WCHAR* GetStringValue() override; + + void Command(const std::wstring& command) override; + +protected: + void ReadOptions(ConfigParser& parser, const WCHAR* section) override; + void UpdateValue() override; + +private: + ParentMeasure* m_Parent; + MeasureType m_Type; +}; + +void SecondsToTime(UINT seconds, bool leadingZero, WCHAR* buffer); + +#endif diff --git a/Library/MeasureRecycleManager.h b/Library/MeasureRecycleManager.h index 4de711ea8..a15f91aad 100644 --- a/Library/MeasureRecycleManager.h +++ b/Library/MeasureRecycleManager.h @@ -1,35 +1,35 @@ -/* Copyright (C) 2016 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#ifndef RM_LIBRARY_MEASURERECYCLEMANAGER_H_ -#define RM_LIBRARY_MEASURERECYCLEMANAGER_H_ - -#include "Measure.h" - -class MeasureRecycleManager : public Measure -{ -public: - MeasureRecycleManager(Skin* skin, const WCHAR* name); - virtual ~MeasureRecycleManager(); - - MeasureRecycleManager(const MeasureRecycleManager& other) = delete; - MeasureRecycleManager& operator=(MeasureRecycleManager other) = delete; - - UINT GetTypeID() override { return TypeID(); } - - void Command(const std::wstring& command) override; - -protected: - void ReadOptions(ConfigParser& parser, const WCHAR* section) override; - void UpdateValue() override; - -private: - enum class Type; - Type m_Type; -}; - -#endif +/* Copyright (C) 2016 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#ifndef RM_LIBRARY_MEASURERECYCLEMANAGER_H_ +#define RM_LIBRARY_MEASURERECYCLEMANAGER_H_ + +#include "Measure.h" + +class MeasureRecycleManager : public Measure +{ +public: + MeasureRecycleManager(Skin* skin, const WCHAR* name); + virtual ~MeasureRecycleManager(); + + MeasureRecycleManager(const MeasureRecycleManager& other) = delete; + MeasureRecycleManager& operator=(MeasureRecycleManager other) = delete; + + UINT GetTypeID() override { return TypeID(); } + + void Command(const std::wstring& command) override; + +protected: + void ReadOptions(ConfigParser& parser, const WCHAR* section) override; + void UpdateValue() override; + +private: + enum class Type; + Type m_Type; +}; + +#endif diff --git a/Library/MeasureWebParser.cpp b/Library/MeasureWebParser.cpp index e7bffa253..be21e35ef 100644 --- a/Library/MeasureWebParser.cpp +++ b/Library/MeasureWebParser.cpp @@ -1,1256 +1,1256 @@ -/* Copyright (C) 2005 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#include "StdAfx.h" -#include "MeasureWebParser.h" -#include "Rainmeter.h" -#include "System.h" -#include "pcre/config.h" -#include "pcre/pcre.h" -#include "../Common/CharacterEntityReference.h" -#include "../Common/StringUtil.h" -#include "../Common/FileUtil.h" - -void ShowError(MeasureWebParser* measure, WCHAR* description); - -class ProxyCachePool -{ -public: - ProxyCachePool(LPCWSTR globalProxyName = nullptr, LPCWSTR globalUserAgent = nullptr) : - m_GlobalProxyName((globalProxyName && *globalProxyName) ? globalProxyName : L"/auto"), - m_GlobalUserAgent((globalUserAgent && *globalUserAgent) ? globalUserAgent : L"Rainmeter WebParser plugin") - { - m_GlobalProxyCache = new ProxyCache( - CreateProxy(m_GlobalProxyName.c_str(), m_GlobalUserAgent.c_str()), - m_GlobalUserAgent, - true); - - _wcslwr(&m_GlobalProxyName[0]); - m_CacheMap.insert(std::make_pair(m_GlobalProxyName, m_GlobalProxyCache)); - //LogDebugF(L"* ADD-GLOBAL: key=%s, handle=0x%p, ref=new, agent=%s", m_GlobalProxyName.c_str(), - // m_GlobalProxyCache->GetCache(), m_GlobalUserAgent.c_str()); - } - - ~ProxyCachePool() - { - for (auto iter = m_CacheMap.begin(); iter != m_CacheMap.end(); ++iter) - { - ProxyCache* cache = (*iter).second; - //LogDebugF(L"* FORCE-REMOVE: key=%s, global=%i, ref=%i, agent=%s", (*iter).first.c_str(), - // cache->IsGlobal(), cache->GetRef(), (*iter).second->GetAgent().c_str()); - delete cache; - } - } - - HINTERNET GetCache(const std::wstring& proxyName, const std::wstring& userAgent) - { - ProxyCache* cache = nullptr; - - std::wstring key = proxyName.empty() ? m_GlobalProxyName : proxyName; - std::wstring agent = userAgent.empty() ? m_GlobalUserAgent : userAgent; - _wcslwr(&key[0]); - - bool found = false; - auto iters = m_CacheMap.equal_range(key); - for (auto it = iters.first; it != iters.second; ++it) - { - if (StringUtil::CaseInsensitiveFind(it->second->GetAgent(), agent) != std::wstring::npos) - { - found = true; - cache = it->second; - break; - } - } - - if (!found) - { - // Create new proxy - cache = new ProxyCache(CreateProxy(key.c_str(), agent.c_str()), agent); - m_CacheMap.insert(std::make_pair(key, cache)); - //LogDebugF(L"* ADD: key=%s, handle=0x%p, ref=new, agent=%s", key.c_str(), cache->GetCache(), agent.c_str()); - return cache->GetCache(); - } - - // Use proxy cache - cache->AddRef(); - //LogDebugF(L"* ADD-REF: key=%s, handle=0x%p, global=%i, ref=%i, agent=%s", - // cache->IsGlobal() ? m_GlobalProxyName.c_str() : proxyName.c_str(), cache->GetCache(), - // cache->IsGlobal(), cache->GetRef(), agent.c_str()); - return cache->GetCache(); - } - - void RemoveCache(const std::wstring& proxyName, const std::wstring& userAgent) - { - std::wstring key = proxyName.empty() ? m_GlobalProxyName : proxyName; - std::wstring agent = userAgent.empty() ? m_GlobalUserAgent : userAgent; - - if (!proxyName.empty()) - { - _wcslwr(&key[0]); - } - - auto iters = m_CacheMap.equal_range(key); - for (auto it = iters.first; it != iters.second; ++it) - { - if (StringUtil::CaseInsensitiveFind(it->second->GetAgent(), agent) != std::wstring::npos) - { - ProxyCache* cache = it->second; - cache->Release(); - //LogDebugF(L"* REMOVE: key=%s, global=%i, ref=%i, agent=%s", - // key.c_str(), cache->IsGlobal(), cache->GetRef(), agent.c_str()); - - if (cache->IsInvalid()) - { - //LogDebugF(L"* EMPTY-ERASE: key=%s, agent=%s", key.c_str(), agent.c_str()); - m_CacheMap.erase(it); - delete cache; - } - - break; - } - } - } - -private: - HINTERNET CreateProxy(LPCWSTR proxyName, LPCWSTR userAgent) - { - DWORD proxyType; - LPCWSTR proxyServer; - - if (_wcsicmp(proxyName, L"/auto") == 0) - { - proxyType = INTERNET_OPEN_TYPE_PRECONFIG; - proxyServer = nullptr; - } - else if (_wcsicmp(proxyName, L"/none") == 0) - { - proxyType = INTERNET_OPEN_TYPE_DIRECT; - proxyServer = nullptr; - } - else - { - proxyType = INTERNET_OPEN_TYPE_PROXY; - proxyServer = proxyName; - } - - HINTERNET handle = InternetOpen(userAgent, proxyType, proxyServer, nullptr, 0); - if (handle) - { - if (GetRainmeter().GetDebug()) - { - LogDebugF( - L"ProxyServer=\"%s\" (type=%s, handle=0x%p) UserAgent=%s", - proxyName, - proxyType == INTERNET_OPEN_TYPE_PRECONFIG ? L"PRECONFIG" : proxyType == INTERNET_OPEN_TYPE_DIRECT ? L"DIRECT" : L"PROXY", - handle, - userAgent); - } - } - else - { - ShowError(nullptr, L"InternetOpen error"); - } - - return handle; - } - - class ProxyCache - { - public: - ProxyCache(HINTERNET handle, std::wstring agent, bool isGlobal = false) : - m_Handle(handle), m_Agent(agent), m_IsGlobal(isGlobal), m_Ref(1) {} - ~ProxyCache() { Dispose(); } - - void AddRef() { if (!IsInvalid()) { ++m_Ref; } } - void Release() { if (m_Ref > 0) { --m_Ref; } if (IsInvalid()) { Dispose(); } } - - bool IsGlobal() { return m_IsGlobal; } - bool IsInvalid() { return (m_Ref <= 0 && !IsGlobal()); } - //int GetRef() { return m_Ref; } - HINTERNET GetCache() { return m_Handle; } - std::wstring& GetAgent() { return m_Agent; } - - private: - ProxyCache() {} - ProxyCache(const ProxyCache& cache) {} - - void Dispose() { if (m_Handle) { InternetCloseHandle(m_Handle); m_Handle = nullptr; } } - - HINTERNET m_Handle; - std::wstring m_Agent; - bool m_IsGlobal; - int m_Ref; - }; - - std::unordered_multimap m_CacheMap; - ProxyCache* m_GlobalProxyCache; - std::wstring m_GlobalProxyName; - std::wstring m_GlobalUserAgent; -}; - -BYTE* DownloadUrl(HINTERNET handle, std::wstring& url, std::wstring& headers, DWORD* dataSize, bool forceReload); - -CRITICAL_SECTION g_CriticalSection; -ProxyCachePool* g_ProxyCachePool = nullptr; -UINT g_InstanceCount = 0; - -static std::vector g_Measures; - -#define OVECCOUNT 300 // should be a multiple of 3 - -void SetupGlobalProxySetting() -{ - if (!g_ProxyCachePool) - { - WCHAR server[MAX_PATH] = { 0 }; - WCHAR agent[MAX_PATH] = { 0 }; - LPCWSTR file = GetRainmeter().GetDataFile().c_str(); - - GetPrivateProfileString(L"WebParser.dll", L"ProxyServer", nullptr, server, MAX_PATH, file); - GetPrivateProfileString(L"WebParser.dll", L"UserAgent", nullptr, agent, MAX_PATH, file); - g_ProxyCachePool = new ProxyCachePool(server, agent); - } -} - -void ClearGlobalProxySetting() -{ - delete g_ProxyCachePool; - g_ProxyCachePool = nullptr; -} - -void SetupProxySetting(ProxySetting& setting, const std::wstring proxyServer, const std::wstring userAgent) -{ - if (g_ProxyCachePool) - { - setting.server = proxyServer; - setting.agent = userAgent; - setting.handle = g_ProxyCachePool->GetCache(setting.server, setting.agent); - } -} - -void ClearProxySetting(ProxySetting& setting) -{ - if (g_ProxyCachePool) - { - g_ProxyCachePool->RemoveCache(setting.server, setting.agent); - } - - setting.handle = nullptr; - setting.server.clear(); - setting.agent.clear(); -} - -MeasureWebParser::MeasureWebParser(Skin* skin, const WCHAR* name) : Measure(skin, name), - m_ThreadHandle(), - m_DlThreadHandle(), - m_Codepage(), - m_StringIndex(), - m_StringIndex2(), - m_DecodeCharacterReference(), - m_Debug(), - m_LogSubstringErrors(), - m_UpdateRate(), - m_UpdateCounter(), - m_Download(), - m_ForceReload() -{ - g_Measures.push_back(this); - - if (g_InstanceCount == 0) - { - System::InitializeCriticalSection(&g_CriticalSection); - SetupGlobalProxySetting(); - } - - // No DynamicVariables support for ProxyServer or UserAgent - SetupProxySetting( - m_Proxy, - GetSkin()->GetParser().ReadString(name, L"ProxyServer", L""), - GetSkin()->GetParser().ReadString(name, L"UserAgent", L"")); - - ++g_InstanceCount; -} - -MeasureWebParser::~MeasureWebParser() -{ - if (m_ThreadHandle) - { - // Thread is killed inside critical section so that itself is not inside one when it is terminated - EnterCriticalSection(&g_CriticalSection); - - TerminateThread(m_ThreadHandle, 0); - m_ThreadHandle = nullptr; - - LeaveCriticalSection(&g_CriticalSection); - } - - if (m_DlThreadHandle) - { - // Thread is killed inside critical section so that itself is not inside one when it is terminated - EnterCriticalSection(&g_CriticalSection); - - TerminateThread(m_DlThreadHandle, 0); - m_DlThreadHandle = nullptr; - - LeaveCriticalSection(&g_CriticalSection); - } - - if (m_DownloadFile.empty()) // cache mode - { - if (!m_DownloadedFile.empty()) - { - // Delete the file - DeleteFile(m_DownloadedFile.c_str()); - } - } - - ClearProxySetting(m_Proxy); - - auto iter = std::find(g_Measures.begin(), g_Measures.end(), this); - g_Measures.erase(iter); - - --g_InstanceCount; - if (g_InstanceCount == 0) - { - // Last one, close all handles - ClearGlobalProxySetting(); - - // Last instance deletes the critical section - DeleteCriticalSection(&g_CriticalSection); - } -} - -void MeasureWebParser::ReadOptions(ConfigParser& parser, const WCHAR* section) -{ - EnterCriticalSection(&g_CriticalSection); - - Measure::ReadOptions(parser, section); - - std::wstring url = parser.ReadString(section, L"Url", L"", false); - - // Parse new-style variables without parsing old-style section variables - if (parser.ContainsNewStyleVariable(url)) - { - parser.ParseVariables(url, ConfigParser::VariableType::Section); - } - - m_Url = url; - - m_Headers.clear(); - size_t hNum = 1; - std::wstring hOption = L"Header"; - std::wstring hValue = parser.ReadString(section, hOption.c_str(), L""); - while (!hValue.empty()) - { - m_Headers += hValue + L"\r\n"; - hOption = L"Header" + std::to_wstring(++hNum); - hValue = parser.ReadString(section, hOption.c_str(), L""); - } - - if (!m_Headers.empty()) - { - m_Headers += L"\r\n"; // Append "\r\n" to last header to denote end of header section - } - - m_RegExp = parser.ReadString(section, L"RegExp", L""); - m_FinishAction = parser.ReadString(section, L"FinishAction", L"", false); - m_OnRegExpErrAction = parser.ReadString(section, L"OnRegExpErrorAction", L"", false); - m_OnConnectErrAction = parser.ReadString(section, L"OnConnectErrorAction", L"", false); - m_OnDownloadErrAction = parser.ReadString(section, L"OnDownloadErrorAction", L"", false); - m_ErrorString = parser.ReadString(section, L"ErrorString", L""); - m_LogSubstringErrors = parser.ReadBool(section, L"LogSubstringErrors", true); - - int index = parser.ReadInt(section, L"StringIndex", 0); - m_StringIndex = index < 0 ? 0 : index; - - index = parser.ReadInt(section, L"StringIndex2", 0); - m_StringIndex2 = index < 0 ? 0 : index; - - m_DecodeCharacterReference = parser.ReadInt(section, L"DecodeCharacterReference", 0); - m_UpdateRate = parser.ReadInt(section, L"UpdateRate", 600); - m_ForceReload = 0 != parser.ReadInt(section, L"ForceReload", 0); - m_Codepage = parser.ReadInt(section, L"CodePage", 0); - if (m_Codepage == 0) - { - m_Codepage = CP_UTF8; - } - - m_Download = 0 != parser.ReadInt(section, L"Download", 0); - if (m_Download) - { - m_DownloadFolder = L"DownloadFile\\"; - GetSkin()->MakePathAbsolute(m_DownloadFolder); - m_DownloadFile = parser.ReadString(section, L"DownloadFile", L""); - } - else - { - m_DownloadFile.clear(); - } - - m_Debug = parser.ReadInt(section, L"Debug", 0); - if (m_Debug == 2) - { - m_DebugFileLocation = parser.ReadString(section, L"Debug2File", L"WebParserDump.txt"); - GetSkin()->MakePathAbsolute(m_DebugFileLocation); - LogNoticeF(this, L"Debug file: %s", m_DebugFileLocation.c_str()); - } - - LeaveCriticalSection(&g_CriticalSection); -} - -void MeasureWebParser::UpdateValue() -{ - if (m_Download && m_RegExp.empty() && m_Url.find(L'[') == std::wstring::npos) - { - // If RegExp is empty download the file that is pointed by the Url - if (m_DlThreadHandle == 0) - { - if (m_UpdateCounter == 0) - { - // Launch a new thread to fetch the web data - unsigned int id; - HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 0, NetworkDownloadThreadProc, this, 0, &id); - if (threadHandle) - { - m_DlThreadHandle = threadHandle; - } - } - - m_UpdateCounter++; - if (m_UpdateCounter >= m_UpdateRate) - { - m_UpdateCounter = 0; - } - } - - // Else download the file pointed by the result string (this is done later) - } - else - { - // Make sure that the thread is not writing to the result at the same time - EnterCriticalSection(&g_CriticalSection); - - if (!m_ResultString.empty()) - { - m_Value = wcstod(m_ResultString.c_str(), nullptr); - } - - LeaveCriticalSection(&g_CriticalSection); - - if (m_Url.size() > 0 && m_Url.find(L'[') == std::wstring::npos) - { - // This is not a reference; need to update. - if (m_ThreadHandle == 0 && m_DlThreadHandle == 0) - { - if (m_UpdateCounter == 0) - { - // Launch a new thread to fetch the web data - unsigned int id; - HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 0, NetworkThreadProc, this, 0, &id); - if (threadHandle) - { - m_ThreadHandle = threadHandle; - } - } - - m_UpdateCounter++; - if (m_UpdateCounter >= m_UpdateRate) - { - m_UpdateCounter = 0; - } - } - } - } -} - -const WCHAR* MeasureWebParser::GetStringValue() -{ - static std::wstring s_ResultString; - - EnterCriticalSection(&g_CriticalSection); - if (m_Download) - { - s_ResultString = m_DownloadedFile; - } - else - { - s_ResultString = m_ResultString; - } - LeaveCriticalSection(&g_CriticalSection); - - return CheckSubstitute(s_ResultString.c_str()); -} - -// Fetches the data from the net and parses the page -unsigned __stdcall MeasureWebParser::NetworkThreadProc(void* pParam) -{ - auto* measure = (MeasureWebParser*)pParam; - DWORD dwSize = 0; - - if (GetRainmeter().GetDebug()) - { - LogDebugF(measure, L"Fetching: %s", measure->m_Url.c_str()); - } - BYTE* data = DownloadUrl(measure->m_Proxy.handle, measure->m_Url, measure->m_Headers, &dwSize, measure->m_ForceReload); - if (!data) - { - ShowError(measure, L"Fetch error"); - - if (!measure->m_OnConnectErrAction.empty()) - { - GetRainmeter().DelayedExecuteCommand(measure->m_OnConnectErrAction.c_str(), measure->GetSkin()); - } - } - else - { - if (measure->m_Debug == 2) - { - // Dump to a file - - FILE* file = _wfopen(measure->m_DebugFileLocation.c_str(), L"wb"); - if (file) - { - fwrite(data, sizeof(BYTE), dwSize, file); - fclose(file); - } - else - { - LogErrorF(measure, L"Failed to dump debug data"); - } - } - - measure->ParseData(data, dwSize); - - free(data); - } - - EnterCriticalSection(&g_CriticalSection); - CloseHandle(measure->m_ThreadHandle); - measure->m_ThreadHandle = 0; - LeaveCriticalSection(&g_CriticalSection); - - return 0; // thread completed successfully -} - -void MeasureWebParser::ParseData(const BYTE* rawData, DWORD rawSize, bool utf16Data) -{ - const int UTF16_CODEPAGE = 1200; - if (m_Codepage == UTF16_CODEPAGE) { - utf16Data = true; - } - - const char* error; - int erroffset; - int ovector[OVECCOUNT]; - int rc; - bool doErrorAction = false; - - // Compile the regular expression in the first argument - pcre16* re = pcre16_compile( - (PCRE_SPTR16)m_RegExp.c_str(), - PCRE_UTF16, &error, &erroffset, nullptr); - if (re != nullptr) - { - // Compilation succeeded: match the subject in the second argument - std::wstring buffer; - auto data = (const WCHAR*)rawData; - DWORD dataLength = rawSize / 2; - if (!utf16Data) - { - buffer = StringUtil::Widen((LPCSTR)rawData, rawSize, m_Codepage); - data = buffer.c_str(); - dataLength = (DWORD)buffer.length(); - } - - rc = pcre16_exec(re, nullptr, (PCRE_SPTR16)data, dataLength, 0, 0, ovector, OVECCOUNT); - if (rc >= 0) - { - if (rc == 0) - { - // The output vector wasn't big enough - LogErrorF(this, L"Too many substrings"); - } - else - { - if (m_StringIndex < rc) - { - if (GetRainmeter().GetDebug() && m_Debug != 0) - { - for (int i = 0; i < rc; ++i) - { - const WCHAR* match = data + ovector[2 * i]; - const int matchLen = min(ovector[2 * i + 1] - ovector[2 * i], 256); - LogDebugF(this, L"Index %2d: %.*s", i, matchLen, match); - } - } - - const WCHAR* match = data + ovector[2 * m_StringIndex]; - int matchLen = ovector[2 * m_StringIndex + 1] - ovector[2 * m_StringIndex]; - EnterCriticalSection(&g_CriticalSection); - m_ResultString.assign(match, matchLen); - CharacterEntityReference::Decode(m_ResultString, m_DecodeCharacterReference); - LeaveCriticalSection(&g_CriticalSection); - } - else - { - if (m_LogSubstringErrors) LogWarningF(this, L"Not enough substrings"); - - // Clear the old result - EnterCriticalSection(&g_CriticalSection); - m_ResultString.clear(); - if (m_Download) - { - if (m_DownloadFile.empty()) // cache mode - { - if (!m_DownloadedFile.empty()) - { - // Delete old downloaded file - DeleteFile(m_DownloadedFile.c_str()); - } - } - m_DownloadedFile.clear(); - } - LeaveCriticalSection(&g_CriticalSection); - } - - // Update the references - auto i = g_Measures.begin(); - std::wstring compareStr = L"["; - compareStr += GetOriginalName(); - compareStr += L']'; - for ( ; i != g_Measures.end(); ++i) - { - if (GetSkin() == (*i)->GetSkin() && - StringUtil::CaseInsensitiveFind((*i)->m_Url, compareStr) != std::wstring::npos) - { - if ((*i)->m_StringIndex < rc) - { - const WCHAR* match = data + ovector[2 * (*i)->m_StringIndex]; - int matchLen = ovector[2 * (*i)->m_StringIndex + 1] - ovector[2 * (*i)->m_StringIndex]; - if (!(*i)->m_RegExp.empty()) - { - // Change the index and parse the substring - int index = (*i)->m_StringIndex; - (*i)->m_StringIndex = (*i)->m_StringIndex2; - (*i)->ParseData((BYTE*)match, matchLen * 2, true); - (*i)->m_StringIndex = index; - } - else - { - // Set the result - EnterCriticalSection(&g_CriticalSection); - - // Substitude the [measure] with result - (*i)->m_ResultString = (*i)->m_Url; - (*i)->m_ResultString.replace( - StringUtil::CaseInsensitiveFind((*i)->m_ResultString, compareStr), - compareStr.size(), match, matchLen); - CharacterEntityReference::Decode((*i)->m_ResultString, (*i)->m_DecodeCharacterReference); - - // Start download threads for the references - if ((*i)->m_Download) - { - // Start the download thread - unsigned int id; - HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 0, NetworkDownloadThreadProc, (*i), 0, &id); - if (threadHandle) - { - (*i)->m_DlThreadHandle = threadHandle; - } - } - - LeaveCriticalSection(&g_CriticalSection); - } - } - else - { - if (m_LogSubstringErrors) LogWarningF(*i, L"Not enough substrings"); - - // Clear the old result - EnterCriticalSection(&g_CriticalSection); - (*i)->m_ResultString.clear(); - if ((*i)->m_Download) - { - if ((*i)->m_DownloadFile.empty()) // cache mode - { - if (!(*i)->m_DownloadedFile.empty()) - { - // Delete old downloaded file - DeleteFile((*i)->m_DownloadedFile.c_str()); - } - } - (*i)->m_DownloadedFile.clear(); - } - LeaveCriticalSection(&g_CriticalSection); - } - } - } - } - } - else - { - // Matching failed: handle error cases - LogErrorF(this, L"RegExp matching error (%d)", rc); - doErrorAction = true; - - EnterCriticalSection(&g_CriticalSection); - m_ResultString = m_ErrorString; - - // Update the references - auto i = g_Measures.begin(); - std::wstring compareStr = L"["; - compareStr += GetOriginalName(); - compareStr += L']'; - for ( ; i != g_Measures.end(); ++i) - { - if ((StringUtil::CaseInsensitiveFind((*i)->m_Url, compareStr) != std::wstring::npos) && - (GetSkin() == (*i)->GetSkin())) - { - (*i)->m_ResultString = (*i)->m_ErrorString; - } - } - LeaveCriticalSection(&g_CriticalSection); - } - - // Release memory used for the compiled pattern - pcre16_free(re); - } - else - { - // Compilation failed. - LogErrorF(this, L"RegExp error at offset %d: %S", erroffset, error); - doErrorAction = true; - } - - if (m_Download) - { - // Start the download thread - unsigned int id; - HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 0, NetworkDownloadThreadProc, this, 0, &id); - if (threadHandle) - { - m_DlThreadHandle = threadHandle; - } - } - - if (doErrorAction && !m_OnRegExpErrAction.empty()) - { - GetRainmeter().DelayedExecuteCommand(m_OnRegExpErrAction.c_str(), GetSkin()); - } - else if (!m_Download && !m_FinishAction.empty()) - { - GetRainmeter().DelayedExecuteCommand(m_FinishAction.c_str(), GetSkin()); - } -} - -// Downloads file from the net -unsigned __stdcall MeasureWebParser::NetworkDownloadThreadProc(void* pParam) -{ - auto* measure = (MeasureWebParser*)pParam; - const bool download = !measure->m_DownloadFile.empty(); - bool ready = false; - - std::wstring url; - - if (measure->m_RegExp.empty() && measure->m_ResultString.empty()) - { - if (!measure->m_Url.empty() && measure->m_Url[0] != L'[') - { - url = measure->m_Url; - } - } - else - { - EnterCriticalSection(&g_CriticalSection); - url = measure->m_ResultString; - LeaveCriticalSection(&g_CriticalSection); - - std::wstring::size_type pos = url.find(L':'); - if (pos == std::wstring::npos && !url.empty()) // No protocol - { - // Add the base url to the string - if (url[0] == L'/') - { - // Absolute path - pos = measure->m_Url.find(L'/', 7); // Assume "http://" (=7) - if (pos != std::wstring::npos) - { - std::wstring path(measure->m_Url.substr(0, pos)); - url = path + url; - } - } - else - { - // Relative path - - pos = measure->m_Url.rfind(L'/'); - if (pos != std::wstring::npos) - { - std::wstring path(measure->m_Url.substr(0, pos + 1)); - url = path + url; - } - } - } - } - - if (!url.empty()) - { - // Create the filename - WCHAR buffer[MAX_PATH] = {0}; - std::wstring fullpath, directory; - - if (download) // download mode - { - PathCanonicalize(buffer, measure->m_DownloadFile.c_str()); - - std::wstring path = buffer; - std::wstring::size_type pos = path.find_first_not_of(L'\\'); - if (pos != std::wstring::npos) - { - path.erase(0, pos); - } - - PathCanonicalize(buffer, measure->m_DownloadFolder.c_str()); - CreateDirectory(buffer, nullptr); // Make sure that the folder exists - - wcscat(buffer, path.c_str()); - - if (buffer[wcslen(buffer)-1] != L'\\') // path is a file - { - fullpath = buffer; - PathRemoveFileSpec(buffer); - } - PathAddBackslash(buffer); - } - else // cache mode - { - GetTempPath(MAX_PATH, buffer); - wcscat(buffer, L"Rainmeter-Cache\\"); // "%TEMP%\Rainmeter-Cache\" - } - CreateDirectory(buffer, nullptr); // Make sure that the folder exists - directory = buffer; - - if (fullpath.empty()) - { - fullpath = directory; - - std::wstring::size_type pos2 = url.find_first_of(L"?#"); - std::wstring::size_type pos1 = url.find_last_of(L'/', pos2); - pos1 = (pos1 != std::wstring::npos) ? pos1 + 1 : 0; - - std::wstring name; - if (pos2 != std::wstring::npos) - { - name.assign(url, pos1, pos2 - pos1); - } - else - { - name.assign(url, pos1, url.length() - pos1); - } - - if (!name.empty()) - { - // Replace reserved characters to "_" - pos1 = 0; - while ((pos1 = name.find_first_of(L"\\/:*?\"<>|", pos1)) != std::wstring::npos) - { - name[pos1] = L'_'; - } - fullpath += name; - } - else - { - fullpath += L"index"; - } - } - - ready = true; - - if (download) // download mode - { - if (!PathFileExists(directory.c_str()) || !PathIsDirectory(directory.c_str())) - { - ready = false; - LogErrorF(measure, L"Directory does not exist: %s", directory.c_str()); - } - else if (PathIsDirectory(fullpath.c_str())) - { - ready = false; - LogErrorF(measure, L"Path is a directory, not a file: %s", fullpath.c_str()); - } - else if (PathFileExists(fullpath.c_str())) - { - DWORD attr = GetFileAttributes(fullpath.c_str()); - if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_READONLY)) - { - ready = false; - LogErrorF(measure, L"File is read-only: %s", fullpath.c_str()); - } - } - } - else // cache mode - { - EnterCriticalSection(&g_CriticalSection); - - if (PathFileExists(fullpath.c_str())) - { - std::wstring::size_type pos = fullpath.find_last_of(L'.'); - - std::wstring path, ext; - if (pos != std::wstring::npos) - { - path.assign(fullpath, 0, pos); - ext.assign(fullpath, pos, fullpath.length() - pos); - } - else - { - path = fullpath; - } - - // Assign a serial number - int i = 1; - do - { - wsprintf(buffer, L"_%i", i++); - - fullpath = path; - fullpath += buffer; - if (!ext.empty()) - { - fullpath += ext; - } - } while (PathFileExists(fullpath.c_str())); - } - - // Create empty file - HANDLE hFile = CreateFile(fullpath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); - - LeaveCriticalSection(&g_CriticalSection); - } - - if (ready) - { - // Delete IE cache before download if "SyncMode5" is not 3 (every visit to the page) - { - // Check "Temporary Internet Files" sync mode (SyncMode5) - // Values: - // Every visit to the page 3 - // Every time you start Internet Explorer 2 - // Automatically (default) 4 - // Never 0 - // http://support.microsoft.com/kb/263070/en - - HKEY hKey; - LONG ret; - DWORD mode; - - ret = RegOpenKeyEx(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", 0, KEY_QUERY_VALUE, &hKey); - if (ret == ERROR_SUCCESS) - { - DWORD size = sizeof(mode); - ret = RegQueryValueEx(hKey, L"SyncMode5", nullptr, nullptr, (LPBYTE)&mode, &size); - RegCloseKey(hKey); - } - - if (ret != ERROR_SUCCESS || mode != 3) - { - std::wstring::size_type pos = url.find_first_of(L'#'); - - if (pos != std::wstring::npos) - { - DeleteUrlCacheEntry(url.substr(0, pos).c_str()); - } - else - { - DeleteUrlCacheEntry(url.c_str()); - } - } - } - - if (GetRainmeter().GetDebug()) - { - LogDebugF(measure, L"Downloading url '%s' to: %s", url.c_str(), fullpath.c_str()); - } - - HRESULT resultCoInitialize = CoInitialize(nullptr); // requires before calling URLDownloadToFile function - - // Download the file - HRESULT result = URLDownloadToFile(nullptr, url.c_str(), fullpath.c_str(), 0, nullptr); - if (result == S_OK) - { - EnterCriticalSection(&g_CriticalSection); - - if (!download) // cache mode - { - if (!measure->m_DownloadedFile.empty()) - { - // Delete old downloaded file - DeleteFile(measure->m_DownloadedFile.c_str()); - } - } - - // Convert LFN to 8.3 filename if the path contains blank character - if (fullpath.find_first_of(L' ') != std::wstring::npos) - { - DWORD size = GetShortPathName(fullpath.c_str(), buffer, MAX_PATH); - if (size > 0 && size <= MAX_PATH) - { - fullpath = buffer; - } - } - measure->m_DownloadedFile = fullpath; - - LeaveCriticalSection(&g_CriticalSection); - - if (!measure->m_FinishAction.empty()) - { - GetRainmeter().DelayedExecuteCommand(measure->m_FinishAction.c_str(), measure->GetSkin()); - } - } - else - { - ready = false; - - if (!download) // cache mode - { - // Delete empty file - DeleteFile(fullpath.c_str()); - } - - LogErrorF( - measure, - L"Download failed (res=0x%08X, COM=0x%08X): %s", - result, resultCoInitialize, url.c_str()); - - if (!measure->m_OnDownloadErrAction.empty()) - { - GetRainmeter().DelayedExecuteCommand(measure->m_OnDownloadErrAction.c_str(), measure->GetSkin()); - } - } - - if (SUCCEEDED(resultCoInitialize)) - { - CoUninitialize(); - } - } - else - { - LogErrorF(measure, L"Download failed: %s", url.c_str()); - - if (!measure->m_OnDownloadErrAction.empty()) - { - GetRainmeter().DelayedExecuteCommand(measure->m_OnDownloadErrAction.c_str(), measure->GetSkin()); - } - } - } - else - { - LogErrorF(measure, L"Url is empty"); - } - - if (!ready) // download failed - { - EnterCriticalSection(&g_CriticalSection); - - if (!download) // cache mode - { - if (!measure->m_DownloadedFile.empty()) - { - // Delete old downloaded file - DeleteFile(measure->m_DownloadedFile.c_str()); - } - } - - // Clear old downloaded filename - measure->m_DownloadedFile.clear(); - - LeaveCriticalSection(&g_CriticalSection); - } - - EnterCriticalSection(&g_CriticalSection); - CloseHandle(measure->m_DlThreadHandle); - measure->m_DlThreadHandle = 0; - LeaveCriticalSection(&g_CriticalSection); - - return 0; // thread completed successfully -} - -/* - Downloads the given url and returns the webpage as dynamically allocated string. - You need to free the returned string after use! -*/ -BYTE* DownloadUrl(HINTERNET handle, std::wstring& url, std::wstring& headers, DWORD* dataSize, bool forceReload) -{ - if (_wcsnicmp(url.c_str(), L"file://", 7) == 0) // Local file - { - WCHAR path[MAX_PATH]; - DWORD pathLength = _countof(path); - HRESULT hr = PathCreateFromUrl(url.c_str(), path, &pathLength, 0); - if (FAILED(hr)) - { - return nullptr; - } - - size_t fileSize = 0; - BYTE* buffer = FileUtil::ReadFullFile(path, &fileSize).release(); - *dataSize = (DWORD)fileSize; - - return buffer; - } - - DWORD flags = INTERNET_FLAG_RESYNCHRONIZE; - if (forceReload) - { - flags = INTERNET_FLAG_RELOAD; - } - - HINTERNET hUrlDump = InternetOpenUrl(handle, url.c_str(), headers.c_str(), -1L, flags, 0); - if (!hUrlDump) - { - return nullptr; - } - - // Allocate buffer with 3 extra bytes for triple null termination in case the string is - // invalid (e.g. when incorrectly using the UTF-16LE codepage for the data). - const int CHUNK_SIZE = 8192; - DWORD bufferSize = CHUNK_SIZE; - BYTE* buffer = (BYTE*)malloc(bufferSize + 3); - *dataSize = 0; - - // Read the data. - do - { - DWORD readSize; - if (!InternetReadFile(hUrlDump, buffer + *dataSize, bufferSize - *dataSize, &readSize)) - { - free(buffer); - InternetCloseHandle(hUrlDump); - return nullptr; - } - else if (readSize == 0) - { - // All data read. - break; - } - - *dataSize += readSize; - - bufferSize += CHUNK_SIZE; - buffer = (BYTE*)realloc(buffer, bufferSize + 3); - } - while (true); - - InternetCloseHandle(hUrlDump); - - // Triple null terminate the buffer. - buffer[*dataSize] = 0; - buffer[*dataSize + 1] = 0; - buffer[*dataSize + 2] = 0; - - return buffer; -} - -/* - Writes the last error to log. -*/ -void ShowError(MeasureWebParser* measure, WCHAR* description) -{ - DWORD dwErr = GetLastError(); - if (dwErr == ERROR_INTERNET_EXTENDED_ERROR) - { - WCHAR szBuffer[1024]; - DWORD dwError, dwLen = 1024; - const WCHAR* error = L"Unknown error"; - if (InternetGetLastResponseInfo(&dwError, szBuffer, &dwLen)) - { - error = szBuffer; - dwErr = dwError; - } - - LogErrorF(measure, L"(%s) %s (ErrorCode=%i)", description, error, dwErr); - } - else - { - LPVOID lpMsgBuf = nullptr; - - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_HMODULE | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS | - FORMAT_MESSAGE_MAX_WIDTH_MASK, - GetModuleHandle(L"wininet"), - dwErr, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language - (LPTSTR) &lpMsgBuf, - 0, - nullptr - ); - - const WCHAR* error = lpMsgBuf ? (WCHAR*)lpMsgBuf : L"Unknown error"; - LogErrorF(measure, L"(%s) %s (ErrorCode=%i)", description, error, dwErr); - - if (lpMsgBuf) LocalFree(lpMsgBuf); - } -} - -void MeasureWebParser::Command(const std::wstring& command) -{ - const WCHAR* args = command.c_str(); - - // Kill the threads (if any) and reset the update counter - if (_wcsicmp(args, L"UPDATE") == 0) - { - if (m_ThreadHandle) - { - // Thread is killed inside critical section so that itself is not inside one when it is terminated - EnterCriticalSection(&g_CriticalSection); - - TerminateThread(m_ThreadHandle, 0); - m_ThreadHandle = nullptr; - - LeaveCriticalSection(&g_CriticalSection); - } - - if (m_DlThreadHandle) - { - // Thread is killed inside critical section so that itself is not inside one when it is terminated - EnterCriticalSection(&g_CriticalSection); - - TerminateThread(m_DlThreadHandle, 0); - m_DlThreadHandle = nullptr; - - LeaveCriticalSection(&g_CriticalSection); - } - - m_UpdateCounter = 0; - } - else if (_wcsicmp(args, L"RESET") == 0) - { - m_ResultString.clear(); - m_DownloadedFile.clear(); - - EnterCriticalSection(&g_CriticalSection); - - // Update the references - auto i = g_Measures.begin(); - std::wstring compareStr = L"["; - compareStr += GetOriginalName(); - compareStr += L']'; - for (; i != g_Measures.end(); ++i) - { - if ((StringUtil::CaseInsensitiveFind((*i)->m_Url, compareStr) != std::wstring::npos) && - (GetSkin() == (*i)->GetSkin())) - { - (*i)->m_ResultString.clear(); - (*i)->m_DownloadedFile.clear(); - } - } - LeaveCriticalSection(&g_CriticalSection); - } -} +/* Copyright (C) 2005 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#include "StdAfx.h" +#include "MeasureWebParser.h" +#include "Rainmeter.h" +#include "System.h" +#include "pcre/config.h" +#include "pcre/pcre.h" +#include "../Common/CharacterEntityReference.h" +#include "../Common/StringUtil.h" +#include "../Common/FileUtil.h" + +void ShowError(MeasureWebParser* measure, WCHAR* description); + +class ProxyCachePool +{ +public: + ProxyCachePool(LPCWSTR globalProxyName = nullptr, LPCWSTR globalUserAgent = nullptr) : + m_GlobalProxyName((globalProxyName && *globalProxyName) ? globalProxyName : L"/auto"), + m_GlobalUserAgent((globalUserAgent && *globalUserAgent) ? globalUserAgent : L"Rainmeter WebParser plugin") + { + m_GlobalProxyCache = new ProxyCache( + CreateProxy(m_GlobalProxyName.c_str(), m_GlobalUserAgent.c_str()), + m_GlobalUserAgent, + true); + + _wcslwr(&m_GlobalProxyName[0]); + m_CacheMap.insert(std::make_pair(m_GlobalProxyName, m_GlobalProxyCache)); + //LogDebugF(L"* ADD-GLOBAL: key=%s, handle=0x%p, ref=new, agent=%s", m_GlobalProxyName.c_str(), + // m_GlobalProxyCache->GetCache(), m_GlobalUserAgent.c_str()); + } + + ~ProxyCachePool() + { + for (auto iter = m_CacheMap.begin(); iter != m_CacheMap.end(); ++iter) + { + ProxyCache* cache = (*iter).second; + //LogDebugF(L"* FORCE-REMOVE: key=%s, global=%i, ref=%i, agent=%s", (*iter).first.c_str(), + // cache->IsGlobal(), cache->GetRef(), (*iter).second->GetAgent().c_str()); + delete cache; + } + } + + HINTERNET GetCache(const std::wstring& proxyName, const std::wstring& userAgent) + { + ProxyCache* cache = nullptr; + + std::wstring key = proxyName.empty() ? m_GlobalProxyName : proxyName; + std::wstring agent = userAgent.empty() ? m_GlobalUserAgent : userAgent; + _wcslwr(&key[0]); + + bool found = false; + auto iters = m_CacheMap.equal_range(key); + for (auto it = iters.first; it != iters.second; ++it) + { + if (StringUtil::CaseInsensitiveFind(it->second->GetAgent(), agent) != std::wstring::npos) + { + found = true; + cache = it->second; + break; + } + } + + if (!found) + { + // Create new proxy + cache = new ProxyCache(CreateProxy(key.c_str(), agent.c_str()), agent); + m_CacheMap.insert(std::make_pair(key, cache)); + //LogDebugF(L"* ADD: key=%s, handle=0x%p, ref=new, agent=%s", key.c_str(), cache->GetCache(), agent.c_str()); + return cache->GetCache(); + } + + // Use proxy cache + cache->AddRef(); + //LogDebugF(L"* ADD-REF: key=%s, handle=0x%p, global=%i, ref=%i, agent=%s", + // cache->IsGlobal() ? m_GlobalProxyName.c_str() : proxyName.c_str(), cache->GetCache(), + // cache->IsGlobal(), cache->GetRef(), agent.c_str()); + return cache->GetCache(); + } + + void RemoveCache(const std::wstring& proxyName, const std::wstring& userAgent) + { + std::wstring key = proxyName.empty() ? m_GlobalProxyName : proxyName; + std::wstring agent = userAgent.empty() ? m_GlobalUserAgent : userAgent; + + if (!proxyName.empty()) + { + _wcslwr(&key[0]); + } + + auto iters = m_CacheMap.equal_range(key); + for (auto it = iters.first; it != iters.second; ++it) + { + if (StringUtil::CaseInsensitiveFind(it->second->GetAgent(), agent) != std::wstring::npos) + { + ProxyCache* cache = it->second; + cache->Release(); + //LogDebugF(L"* REMOVE: key=%s, global=%i, ref=%i, agent=%s", + // key.c_str(), cache->IsGlobal(), cache->GetRef(), agent.c_str()); + + if (cache->IsInvalid()) + { + //LogDebugF(L"* EMPTY-ERASE: key=%s, agent=%s", key.c_str(), agent.c_str()); + m_CacheMap.erase(it); + delete cache; + } + + break; + } + } + } + +private: + HINTERNET CreateProxy(LPCWSTR proxyName, LPCWSTR userAgent) + { + DWORD proxyType; + LPCWSTR proxyServer; + + if (_wcsicmp(proxyName, L"/auto") == 0) + { + proxyType = INTERNET_OPEN_TYPE_PRECONFIG; + proxyServer = nullptr; + } + else if (_wcsicmp(proxyName, L"/none") == 0) + { + proxyType = INTERNET_OPEN_TYPE_DIRECT; + proxyServer = nullptr; + } + else + { + proxyType = INTERNET_OPEN_TYPE_PROXY; + proxyServer = proxyName; + } + + HINTERNET handle = InternetOpen(userAgent, proxyType, proxyServer, nullptr, 0); + if (handle) + { + if (GetRainmeter().GetDebug()) + { + LogDebugF( + L"ProxyServer=\"%s\" (type=%s, handle=0x%p) UserAgent=%s", + proxyName, + proxyType == INTERNET_OPEN_TYPE_PRECONFIG ? L"PRECONFIG" : proxyType == INTERNET_OPEN_TYPE_DIRECT ? L"DIRECT" : L"PROXY", + handle, + userAgent); + } + } + else + { + ShowError(nullptr, L"InternetOpen error"); + } + + return handle; + } + + class ProxyCache + { + public: + ProxyCache(HINTERNET handle, std::wstring agent, bool isGlobal = false) : + m_Handle(handle), m_Agent(agent), m_IsGlobal(isGlobal), m_Ref(1) {} + ~ProxyCache() { Dispose(); } + + void AddRef() { if (!IsInvalid()) { ++m_Ref; } } + void Release() { if (m_Ref > 0) { --m_Ref; } if (IsInvalid()) { Dispose(); } } + + bool IsGlobal() { return m_IsGlobal; } + bool IsInvalid() { return (m_Ref <= 0 && !IsGlobal()); } + //int GetRef() { return m_Ref; } + HINTERNET GetCache() { return m_Handle; } + std::wstring& GetAgent() { return m_Agent; } + + private: + ProxyCache() {} + ProxyCache(const ProxyCache& cache) {} + + void Dispose() { if (m_Handle) { InternetCloseHandle(m_Handle); m_Handle = nullptr; } } + + HINTERNET m_Handle; + std::wstring m_Agent; + bool m_IsGlobal; + int m_Ref; + }; + + std::unordered_multimap m_CacheMap; + ProxyCache* m_GlobalProxyCache; + std::wstring m_GlobalProxyName; + std::wstring m_GlobalUserAgent; +}; + +BYTE* DownloadUrl(HINTERNET handle, std::wstring& url, std::wstring& headers, DWORD* dataSize, bool forceReload); + +CRITICAL_SECTION g_CriticalSection; +ProxyCachePool* g_ProxyCachePool = nullptr; +UINT g_InstanceCount = 0; + +static std::vector g_Measures; + +#define OVECCOUNT 300 // should be a multiple of 3 + +void SetupGlobalProxySetting() +{ + if (!g_ProxyCachePool) + { + WCHAR server[MAX_PATH] = { 0 }; + WCHAR agent[MAX_PATH] = { 0 }; + LPCWSTR file = GetRainmeter().GetDataFile().c_str(); + + GetPrivateProfileString(L"WebParser.dll", L"ProxyServer", nullptr, server, MAX_PATH, file); + GetPrivateProfileString(L"WebParser.dll", L"UserAgent", nullptr, agent, MAX_PATH, file); + g_ProxyCachePool = new ProxyCachePool(server, agent); + } +} + +void ClearGlobalProxySetting() +{ + delete g_ProxyCachePool; + g_ProxyCachePool = nullptr; +} + +void SetupProxySetting(ProxySetting& setting, const std::wstring proxyServer, const std::wstring userAgent) +{ + if (g_ProxyCachePool) + { + setting.server = proxyServer; + setting.agent = userAgent; + setting.handle = g_ProxyCachePool->GetCache(setting.server, setting.agent); + } +} + +void ClearProxySetting(ProxySetting& setting) +{ + if (g_ProxyCachePool) + { + g_ProxyCachePool->RemoveCache(setting.server, setting.agent); + } + + setting.handle = nullptr; + setting.server.clear(); + setting.agent.clear(); +} + +MeasureWebParser::MeasureWebParser(Skin* skin, const WCHAR* name) : Measure(skin, name), + m_ThreadHandle(), + m_DlThreadHandle(), + m_Codepage(), + m_StringIndex(), + m_StringIndex2(), + m_DecodeCharacterReference(), + m_Debug(), + m_LogSubstringErrors(), + m_UpdateRate(), + m_UpdateCounter(), + m_Download(), + m_ForceReload() +{ + g_Measures.push_back(this); + + if (g_InstanceCount == 0) + { + System::InitializeCriticalSection(&g_CriticalSection); + SetupGlobalProxySetting(); + } + + // No DynamicVariables support for ProxyServer or UserAgent + SetupProxySetting( + m_Proxy, + GetSkin()->GetParser().ReadString(name, L"ProxyServer", L""), + GetSkin()->GetParser().ReadString(name, L"UserAgent", L"")); + + ++g_InstanceCount; +} + +MeasureWebParser::~MeasureWebParser() +{ + if (m_ThreadHandle) + { + // Thread is killed inside critical section so that itself is not inside one when it is terminated + EnterCriticalSection(&g_CriticalSection); + + TerminateThread(m_ThreadHandle, 0); + m_ThreadHandle = nullptr; + + LeaveCriticalSection(&g_CriticalSection); + } + + if (m_DlThreadHandle) + { + // Thread is killed inside critical section so that itself is not inside one when it is terminated + EnterCriticalSection(&g_CriticalSection); + + TerminateThread(m_DlThreadHandle, 0); + m_DlThreadHandle = nullptr; + + LeaveCriticalSection(&g_CriticalSection); + } + + if (m_DownloadFile.empty()) // cache mode + { + if (!m_DownloadedFile.empty()) + { + // Delete the file + DeleteFile(m_DownloadedFile.c_str()); + } + } + + ClearProxySetting(m_Proxy); + + auto iter = std::find(g_Measures.begin(), g_Measures.end(), this); + g_Measures.erase(iter); + + --g_InstanceCount; + if (g_InstanceCount == 0) + { + // Last one, close all handles + ClearGlobalProxySetting(); + + // Last instance deletes the critical section + DeleteCriticalSection(&g_CriticalSection); + } +} + +void MeasureWebParser::ReadOptions(ConfigParser& parser, const WCHAR* section) +{ + EnterCriticalSection(&g_CriticalSection); + + Measure::ReadOptions(parser, section); + + std::wstring url = parser.ReadString(section, L"Url", L"", false); + + // Parse new-style variables without parsing old-style section variables + if (parser.ContainsNewStyleVariable(url)) + { + parser.ParseVariables(url, ConfigParser::VariableType::Section); + } + + m_Url = url; + + m_Headers.clear(); + size_t hNum = 1; + std::wstring hOption = L"Header"; + std::wstring hValue = parser.ReadString(section, hOption.c_str(), L""); + while (!hValue.empty()) + { + m_Headers += hValue + L"\r\n"; + hOption = L"Header" + std::to_wstring(++hNum); + hValue = parser.ReadString(section, hOption.c_str(), L""); + } + + if (!m_Headers.empty()) + { + m_Headers += L"\r\n"; // Append "\r\n" to last header to denote end of header section + } + + m_RegExp = parser.ReadString(section, L"RegExp", L""); + m_FinishAction = parser.ReadString(section, L"FinishAction", L"", false); + m_OnRegExpErrAction = parser.ReadString(section, L"OnRegExpErrorAction", L"", false); + m_OnConnectErrAction = parser.ReadString(section, L"OnConnectErrorAction", L"", false); + m_OnDownloadErrAction = parser.ReadString(section, L"OnDownloadErrorAction", L"", false); + m_ErrorString = parser.ReadString(section, L"ErrorString", L""); + m_LogSubstringErrors = parser.ReadBool(section, L"LogSubstringErrors", true); + + int index = parser.ReadInt(section, L"StringIndex", 0); + m_StringIndex = index < 0 ? 0 : index; + + index = parser.ReadInt(section, L"StringIndex2", 0); + m_StringIndex2 = index < 0 ? 0 : index; + + m_DecodeCharacterReference = parser.ReadInt(section, L"DecodeCharacterReference", 0); + m_UpdateRate = parser.ReadInt(section, L"UpdateRate", 600); + m_ForceReload = 0 != parser.ReadInt(section, L"ForceReload", 0); + m_Codepage = parser.ReadInt(section, L"CodePage", 0); + if (m_Codepage == 0) + { + m_Codepage = CP_UTF8; + } + + m_Download = 0 != parser.ReadInt(section, L"Download", 0); + if (m_Download) + { + m_DownloadFolder = L"DownloadFile\\"; + GetSkin()->MakePathAbsolute(m_DownloadFolder); + m_DownloadFile = parser.ReadString(section, L"DownloadFile", L""); + } + else + { + m_DownloadFile.clear(); + } + + m_Debug = parser.ReadInt(section, L"Debug", 0); + if (m_Debug == 2) + { + m_DebugFileLocation = parser.ReadString(section, L"Debug2File", L"WebParserDump.txt"); + GetSkin()->MakePathAbsolute(m_DebugFileLocation); + LogNoticeF(this, L"Debug file: %s", m_DebugFileLocation.c_str()); + } + + LeaveCriticalSection(&g_CriticalSection); +} + +void MeasureWebParser::UpdateValue() +{ + if (m_Download && m_RegExp.empty() && m_Url.find(L'[') == std::wstring::npos) + { + // If RegExp is empty download the file that is pointed by the Url + if (m_DlThreadHandle == 0) + { + if (m_UpdateCounter == 0) + { + // Launch a new thread to fetch the web data + unsigned int id; + HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 0, NetworkDownloadThreadProc, this, 0, &id); + if (threadHandle) + { + m_DlThreadHandle = threadHandle; + } + } + + m_UpdateCounter++; + if (m_UpdateCounter >= m_UpdateRate) + { + m_UpdateCounter = 0; + } + } + + // Else download the file pointed by the result string (this is done later) + } + else + { + // Make sure that the thread is not writing to the result at the same time + EnterCriticalSection(&g_CriticalSection); + + if (!m_ResultString.empty()) + { + m_Value = wcstod(m_ResultString.c_str(), nullptr); + } + + LeaveCriticalSection(&g_CriticalSection); + + if (m_Url.size() > 0 && m_Url.find(L'[') == std::wstring::npos) + { + // This is not a reference; need to update. + if (m_ThreadHandle == 0 && m_DlThreadHandle == 0) + { + if (m_UpdateCounter == 0) + { + // Launch a new thread to fetch the web data + unsigned int id; + HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 0, NetworkThreadProc, this, 0, &id); + if (threadHandle) + { + m_ThreadHandle = threadHandle; + } + } + + m_UpdateCounter++; + if (m_UpdateCounter >= m_UpdateRate) + { + m_UpdateCounter = 0; + } + } + } + } +} + +const WCHAR* MeasureWebParser::GetStringValue() +{ + static std::wstring s_ResultString; + + EnterCriticalSection(&g_CriticalSection); + if (m_Download) + { + s_ResultString = m_DownloadedFile; + } + else + { + s_ResultString = m_ResultString; + } + LeaveCriticalSection(&g_CriticalSection); + + return CheckSubstitute(s_ResultString.c_str()); +} + +// Fetches the data from the net and parses the page +unsigned __stdcall MeasureWebParser::NetworkThreadProc(void* pParam) +{ + auto* measure = (MeasureWebParser*)pParam; + DWORD dwSize = 0; + + if (GetRainmeter().GetDebug()) + { + LogDebugF(measure, L"Fetching: %s", measure->m_Url.c_str()); + } + BYTE* data = DownloadUrl(measure->m_Proxy.handle, measure->m_Url, measure->m_Headers, &dwSize, measure->m_ForceReload); + if (!data) + { + ShowError(measure, L"Fetch error"); + + if (!measure->m_OnConnectErrAction.empty()) + { + GetRainmeter().DelayedExecuteCommand(measure->m_OnConnectErrAction.c_str(), measure->GetSkin()); + } + } + else + { + if (measure->m_Debug == 2) + { + // Dump to a file + + FILE* file = _wfopen(measure->m_DebugFileLocation.c_str(), L"wb"); + if (file) + { + fwrite(data, sizeof(BYTE), dwSize, file); + fclose(file); + } + else + { + LogErrorF(measure, L"Failed to dump debug data"); + } + } + + measure->ParseData(data, dwSize); + + free(data); + } + + EnterCriticalSection(&g_CriticalSection); + CloseHandle(measure->m_ThreadHandle); + measure->m_ThreadHandle = 0; + LeaveCriticalSection(&g_CriticalSection); + + return 0; // thread completed successfully +} + +void MeasureWebParser::ParseData(const BYTE* rawData, DWORD rawSize, bool utf16Data) +{ + const int UTF16_CODEPAGE = 1200; + if (m_Codepage == UTF16_CODEPAGE) { + utf16Data = true; + } + + const char* error; + int erroffset; + int ovector[OVECCOUNT]; + int rc; + bool doErrorAction = false; + + // Compile the regular expression in the first argument + pcre16* re = pcre16_compile( + (PCRE_SPTR16)m_RegExp.c_str(), + PCRE_UTF16, &error, &erroffset, nullptr); + if (re != nullptr) + { + // Compilation succeeded: match the subject in the second argument + std::wstring buffer; + auto data = (const WCHAR*)rawData; + DWORD dataLength = rawSize / 2; + if (!utf16Data) + { + buffer = StringUtil::Widen((LPCSTR)rawData, rawSize, m_Codepage); + data = buffer.c_str(); + dataLength = (DWORD)buffer.length(); + } + + rc = pcre16_exec(re, nullptr, (PCRE_SPTR16)data, dataLength, 0, 0, ovector, OVECCOUNT); + if (rc >= 0) + { + if (rc == 0) + { + // The output vector wasn't big enough + LogErrorF(this, L"Too many substrings"); + } + else + { + if (m_StringIndex < rc) + { + if (GetRainmeter().GetDebug() && m_Debug != 0) + { + for (int i = 0; i < rc; ++i) + { + const WCHAR* match = data + ovector[2 * i]; + const int matchLen = min(ovector[2 * i + 1] - ovector[2 * i], 256); + LogDebugF(this, L"Index %2d: %.*s", i, matchLen, match); + } + } + + const WCHAR* match = data + ovector[2 * m_StringIndex]; + int matchLen = ovector[2 * m_StringIndex + 1] - ovector[2 * m_StringIndex]; + EnterCriticalSection(&g_CriticalSection); + m_ResultString.assign(match, matchLen); + CharacterEntityReference::Decode(m_ResultString, m_DecodeCharacterReference); + LeaveCriticalSection(&g_CriticalSection); + } + else + { + if (m_LogSubstringErrors) LogWarningF(this, L"Not enough substrings"); + + // Clear the old result + EnterCriticalSection(&g_CriticalSection); + m_ResultString.clear(); + if (m_Download) + { + if (m_DownloadFile.empty()) // cache mode + { + if (!m_DownloadedFile.empty()) + { + // Delete old downloaded file + DeleteFile(m_DownloadedFile.c_str()); + } + } + m_DownloadedFile.clear(); + } + LeaveCriticalSection(&g_CriticalSection); + } + + // Update the references + auto i = g_Measures.begin(); + std::wstring compareStr = L"["; + compareStr += GetOriginalName(); + compareStr += L']'; + for ( ; i != g_Measures.end(); ++i) + { + if (GetSkin() == (*i)->GetSkin() && + StringUtil::CaseInsensitiveFind((*i)->m_Url, compareStr) != std::wstring::npos) + { + if ((*i)->m_StringIndex < rc) + { + const WCHAR* match = data + ovector[2 * (*i)->m_StringIndex]; + int matchLen = ovector[2 * (*i)->m_StringIndex + 1] - ovector[2 * (*i)->m_StringIndex]; + if (!(*i)->m_RegExp.empty()) + { + // Change the index and parse the substring + int index = (*i)->m_StringIndex; + (*i)->m_StringIndex = (*i)->m_StringIndex2; + (*i)->ParseData((BYTE*)match, matchLen * 2, true); + (*i)->m_StringIndex = index; + } + else + { + // Set the result + EnterCriticalSection(&g_CriticalSection); + + // Substitude the [measure] with result + (*i)->m_ResultString = (*i)->m_Url; + (*i)->m_ResultString.replace( + StringUtil::CaseInsensitiveFind((*i)->m_ResultString, compareStr), + compareStr.size(), match, matchLen); + CharacterEntityReference::Decode((*i)->m_ResultString, (*i)->m_DecodeCharacterReference); + + // Start download threads for the references + if ((*i)->m_Download) + { + // Start the download thread + unsigned int id; + HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 0, NetworkDownloadThreadProc, (*i), 0, &id); + if (threadHandle) + { + (*i)->m_DlThreadHandle = threadHandle; + } + } + + LeaveCriticalSection(&g_CriticalSection); + } + } + else + { + if (m_LogSubstringErrors) LogWarningF(*i, L"Not enough substrings"); + + // Clear the old result + EnterCriticalSection(&g_CriticalSection); + (*i)->m_ResultString.clear(); + if ((*i)->m_Download) + { + if ((*i)->m_DownloadFile.empty()) // cache mode + { + if (!(*i)->m_DownloadedFile.empty()) + { + // Delete old downloaded file + DeleteFile((*i)->m_DownloadedFile.c_str()); + } + } + (*i)->m_DownloadedFile.clear(); + } + LeaveCriticalSection(&g_CriticalSection); + } + } + } + } + } + else + { + // Matching failed: handle error cases + LogErrorF(this, L"RegExp matching error (%d)", rc); + doErrorAction = true; + + EnterCriticalSection(&g_CriticalSection); + m_ResultString = m_ErrorString; + + // Update the references + auto i = g_Measures.begin(); + std::wstring compareStr = L"["; + compareStr += GetOriginalName(); + compareStr += L']'; + for ( ; i != g_Measures.end(); ++i) + { + if ((StringUtil::CaseInsensitiveFind((*i)->m_Url, compareStr) != std::wstring::npos) && + (GetSkin() == (*i)->GetSkin())) + { + (*i)->m_ResultString = (*i)->m_ErrorString; + } + } + LeaveCriticalSection(&g_CriticalSection); + } + + // Release memory used for the compiled pattern + pcre16_free(re); + } + else + { + // Compilation failed. + LogErrorF(this, L"RegExp error at offset %d: %S", erroffset, error); + doErrorAction = true; + } + + if (m_Download) + { + // Start the download thread + unsigned int id; + HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 0, NetworkDownloadThreadProc, this, 0, &id); + if (threadHandle) + { + m_DlThreadHandle = threadHandle; + } + } + + if (doErrorAction && !m_OnRegExpErrAction.empty()) + { + GetRainmeter().DelayedExecuteCommand(m_OnRegExpErrAction.c_str(), GetSkin()); + } + else if (!m_Download && !m_FinishAction.empty()) + { + GetRainmeter().DelayedExecuteCommand(m_FinishAction.c_str(), GetSkin()); + } +} + +// Downloads file from the net +unsigned __stdcall MeasureWebParser::NetworkDownloadThreadProc(void* pParam) +{ + auto* measure = (MeasureWebParser*)pParam; + const bool download = !measure->m_DownloadFile.empty(); + bool ready = false; + + std::wstring url; + + if (measure->m_RegExp.empty() && measure->m_ResultString.empty()) + { + if (!measure->m_Url.empty() && measure->m_Url[0] != L'[') + { + url = measure->m_Url; + } + } + else + { + EnterCriticalSection(&g_CriticalSection); + url = measure->m_ResultString; + LeaveCriticalSection(&g_CriticalSection); + + std::wstring::size_type pos = url.find(L':'); + if (pos == std::wstring::npos && !url.empty()) // No protocol + { + // Add the base url to the string + if (url[0] == L'/') + { + // Absolute path + pos = measure->m_Url.find(L'/', 7); // Assume "http://" (=7) + if (pos != std::wstring::npos) + { + std::wstring path(measure->m_Url.substr(0, pos)); + url = path + url; + } + } + else + { + // Relative path + + pos = measure->m_Url.rfind(L'/'); + if (pos != std::wstring::npos) + { + std::wstring path(measure->m_Url.substr(0, pos + 1)); + url = path + url; + } + } + } + } + + if (!url.empty()) + { + // Create the filename + WCHAR buffer[MAX_PATH] = {0}; + std::wstring fullpath, directory; + + if (download) // download mode + { + PathCanonicalize(buffer, measure->m_DownloadFile.c_str()); + + std::wstring path = buffer; + std::wstring::size_type pos = path.find_first_not_of(L'\\'); + if (pos != std::wstring::npos) + { + path.erase(0, pos); + } + + PathCanonicalize(buffer, measure->m_DownloadFolder.c_str()); + CreateDirectory(buffer, nullptr); // Make sure that the folder exists + + wcscat(buffer, path.c_str()); + + if (buffer[wcslen(buffer)-1] != L'\\') // path is a file + { + fullpath = buffer; + PathRemoveFileSpec(buffer); + } + PathAddBackslash(buffer); + } + else // cache mode + { + GetTempPath(MAX_PATH, buffer); + wcscat(buffer, L"Rainmeter-Cache\\"); // "%TEMP%\Rainmeter-Cache\" + } + CreateDirectory(buffer, nullptr); // Make sure that the folder exists + directory = buffer; + + if (fullpath.empty()) + { + fullpath = directory; + + std::wstring::size_type pos2 = url.find_first_of(L"?#"); + std::wstring::size_type pos1 = url.find_last_of(L'/', pos2); + pos1 = (pos1 != std::wstring::npos) ? pos1 + 1 : 0; + + std::wstring name; + if (pos2 != std::wstring::npos) + { + name.assign(url, pos1, pos2 - pos1); + } + else + { + name.assign(url, pos1, url.length() - pos1); + } + + if (!name.empty()) + { + // Replace reserved characters to "_" + pos1 = 0; + while ((pos1 = name.find_first_of(L"\\/:*?\"<>|", pos1)) != std::wstring::npos) + { + name[pos1] = L'_'; + } + fullpath += name; + } + else + { + fullpath += L"index"; + } + } + + ready = true; + + if (download) // download mode + { + if (!PathFileExists(directory.c_str()) || !PathIsDirectory(directory.c_str())) + { + ready = false; + LogErrorF(measure, L"Directory does not exist: %s", directory.c_str()); + } + else if (PathIsDirectory(fullpath.c_str())) + { + ready = false; + LogErrorF(measure, L"Path is a directory, not a file: %s", fullpath.c_str()); + } + else if (PathFileExists(fullpath.c_str())) + { + DWORD attr = GetFileAttributes(fullpath.c_str()); + if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_READONLY)) + { + ready = false; + LogErrorF(measure, L"File is read-only: %s", fullpath.c_str()); + } + } + } + else // cache mode + { + EnterCriticalSection(&g_CriticalSection); + + if (PathFileExists(fullpath.c_str())) + { + std::wstring::size_type pos = fullpath.find_last_of(L'.'); + + std::wstring path, ext; + if (pos != std::wstring::npos) + { + path.assign(fullpath, 0, pos); + ext.assign(fullpath, pos, fullpath.length() - pos); + } + else + { + path = fullpath; + } + + // Assign a serial number + int i = 1; + do + { + wsprintf(buffer, L"_%i", i++); + + fullpath = path; + fullpath += buffer; + if (!ext.empty()) + { + fullpath += ext; + } + } while (PathFileExists(fullpath.c_str())); + } + + // Create empty file + HANDLE hFile = CreateFile(fullpath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); + + LeaveCriticalSection(&g_CriticalSection); + } + + if (ready) + { + // Delete IE cache before download if "SyncMode5" is not 3 (every visit to the page) + { + // Check "Temporary Internet Files" sync mode (SyncMode5) + // Values: + // Every visit to the page 3 + // Every time you start Internet Explorer 2 + // Automatically (default) 4 + // Never 0 + // http://support.microsoft.com/kb/263070/en + + HKEY hKey; + LONG ret; + DWORD mode; + + ret = RegOpenKeyEx(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", 0, KEY_QUERY_VALUE, &hKey); + if (ret == ERROR_SUCCESS) + { + DWORD size = sizeof(mode); + ret = RegQueryValueEx(hKey, L"SyncMode5", nullptr, nullptr, (LPBYTE)&mode, &size); + RegCloseKey(hKey); + } + + if (ret != ERROR_SUCCESS || mode != 3) + { + std::wstring::size_type pos = url.find_first_of(L'#'); + + if (pos != std::wstring::npos) + { + DeleteUrlCacheEntry(url.substr(0, pos).c_str()); + } + else + { + DeleteUrlCacheEntry(url.c_str()); + } + } + } + + if (GetRainmeter().GetDebug()) + { + LogDebugF(measure, L"Downloading url '%s' to: %s", url.c_str(), fullpath.c_str()); + } + + HRESULT resultCoInitialize = CoInitialize(nullptr); // requires before calling URLDownloadToFile function + + // Download the file + HRESULT result = URLDownloadToFile(nullptr, url.c_str(), fullpath.c_str(), 0, nullptr); + if (result == S_OK) + { + EnterCriticalSection(&g_CriticalSection); + + if (!download) // cache mode + { + if (!measure->m_DownloadedFile.empty()) + { + // Delete old downloaded file + DeleteFile(measure->m_DownloadedFile.c_str()); + } + } + + // Convert LFN to 8.3 filename if the path contains blank character + if (fullpath.find_first_of(L' ') != std::wstring::npos) + { + DWORD size = GetShortPathName(fullpath.c_str(), buffer, MAX_PATH); + if (size > 0 && size <= MAX_PATH) + { + fullpath = buffer; + } + } + measure->m_DownloadedFile = fullpath; + + LeaveCriticalSection(&g_CriticalSection); + + if (!measure->m_FinishAction.empty()) + { + GetRainmeter().DelayedExecuteCommand(measure->m_FinishAction.c_str(), measure->GetSkin()); + } + } + else + { + ready = false; + + if (!download) // cache mode + { + // Delete empty file + DeleteFile(fullpath.c_str()); + } + + LogErrorF( + measure, + L"Download failed (res=0x%08X, COM=0x%08X): %s", + result, resultCoInitialize, url.c_str()); + + if (!measure->m_OnDownloadErrAction.empty()) + { + GetRainmeter().DelayedExecuteCommand(measure->m_OnDownloadErrAction.c_str(), measure->GetSkin()); + } + } + + if (SUCCEEDED(resultCoInitialize)) + { + CoUninitialize(); + } + } + else + { + LogErrorF(measure, L"Download failed: %s", url.c_str()); + + if (!measure->m_OnDownloadErrAction.empty()) + { + GetRainmeter().DelayedExecuteCommand(measure->m_OnDownloadErrAction.c_str(), measure->GetSkin()); + } + } + } + else + { + LogErrorF(measure, L"Url is empty"); + } + + if (!ready) // download failed + { + EnterCriticalSection(&g_CriticalSection); + + if (!download) // cache mode + { + if (!measure->m_DownloadedFile.empty()) + { + // Delete old downloaded file + DeleteFile(measure->m_DownloadedFile.c_str()); + } + } + + // Clear old downloaded filename + measure->m_DownloadedFile.clear(); + + LeaveCriticalSection(&g_CriticalSection); + } + + EnterCriticalSection(&g_CriticalSection); + CloseHandle(measure->m_DlThreadHandle); + measure->m_DlThreadHandle = 0; + LeaveCriticalSection(&g_CriticalSection); + + return 0; // thread completed successfully +} + +/* + Downloads the given url and returns the webpage as dynamically allocated string. + You need to free the returned string after use! +*/ +BYTE* DownloadUrl(HINTERNET handle, std::wstring& url, std::wstring& headers, DWORD* dataSize, bool forceReload) +{ + if (_wcsnicmp(url.c_str(), L"file://", 7) == 0) // Local file + { + WCHAR path[MAX_PATH]; + DWORD pathLength = _countof(path); + HRESULT hr = PathCreateFromUrl(url.c_str(), path, &pathLength, 0); + if (FAILED(hr)) + { + return nullptr; + } + + size_t fileSize = 0; + BYTE* buffer = FileUtil::ReadFullFile(path, &fileSize).release(); + *dataSize = (DWORD)fileSize; + + return buffer; + } + + DWORD flags = INTERNET_FLAG_RESYNCHRONIZE; + if (forceReload) + { + flags = INTERNET_FLAG_RELOAD; + } + + HINTERNET hUrlDump = InternetOpenUrl(handle, url.c_str(), headers.c_str(), -1L, flags, 0); + if (!hUrlDump) + { + return nullptr; + } + + // Allocate buffer with 3 extra bytes for triple null termination in case the string is + // invalid (e.g. when incorrectly using the UTF-16LE codepage for the data). + const int CHUNK_SIZE = 8192; + DWORD bufferSize = CHUNK_SIZE; + BYTE* buffer = (BYTE*)malloc(bufferSize + 3); + *dataSize = 0; + + // Read the data. + do + { + DWORD readSize; + if (!InternetReadFile(hUrlDump, buffer + *dataSize, bufferSize - *dataSize, &readSize)) + { + free(buffer); + InternetCloseHandle(hUrlDump); + return nullptr; + } + else if (readSize == 0) + { + // All data read. + break; + } + + *dataSize += readSize; + + bufferSize += CHUNK_SIZE; + buffer = (BYTE*)realloc(buffer, bufferSize + 3); + } + while (true); + + InternetCloseHandle(hUrlDump); + + // Triple null terminate the buffer. + buffer[*dataSize] = 0; + buffer[*dataSize + 1] = 0; + buffer[*dataSize + 2] = 0; + + return buffer; +} + +/* + Writes the last error to log. +*/ +void ShowError(MeasureWebParser* measure, WCHAR* description) +{ + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_INTERNET_EXTENDED_ERROR) + { + WCHAR szBuffer[1024]; + DWORD dwError, dwLen = 1024; + const WCHAR* error = L"Unknown error"; + if (InternetGetLastResponseInfo(&dwError, szBuffer, &dwLen)) + { + error = szBuffer; + dwErr = dwError; + } + + LogErrorF(measure, L"(%s) %s (ErrorCode=%i)", description, error, dwErr); + } + else + { + LPVOID lpMsgBuf = nullptr; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_MAX_WIDTH_MASK, + GetModuleHandle(L"wininet"), + dwErr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + nullptr + ); + + const WCHAR* error = lpMsgBuf ? (WCHAR*)lpMsgBuf : L"Unknown error"; + LogErrorF(measure, L"(%s) %s (ErrorCode=%i)", description, error, dwErr); + + if (lpMsgBuf) LocalFree(lpMsgBuf); + } +} + +void MeasureWebParser::Command(const std::wstring& command) +{ + const WCHAR* args = command.c_str(); + + // Kill the threads (if any) and reset the update counter + if (_wcsicmp(args, L"UPDATE") == 0) + { + if (m_ThreadHandle) + { + // Thread is killed inside critical section so that itself is not inside one when it is terminated + EnterCriticalSection(&g_CriticalSection); + + TerminateThread(m_ThreadHandle, 0); + m_ThreadHandle = nullptr; + + LeaveCriticalSection(&g_CriticalSection); + } + + if (m_DlThreadHandle) + { + // Thread is killed inside critical section so that itself is not inside one when it is terminated + EnterCriticalSection(&g_CriticalSection); + + TerminateThread(m_DlThreadHandle, 0); + m_DlThreadHandle = nullptr; + + LeaveCriticalSection(&g_CriticalSection); + } + + m_UpdateCounter = 0; + } + else if (_wcsicmp(args, L"RESET") == 0) + { + m_ResultString.clear(); + m_DownloadedFile.clear(); + + EnterCriticalSection(&g_CriticalSection); + + // Update the references + auto i = g_Measures.begin(); + std::wstring compareStr = L"["; + compareStr += GetOriginalName(); + compareStr += L']'; + for (; i != g_Measures.end(); ++i) + { + if ((StringUtil::CaseInsensitiveFind((*i)->m_Url, compareStr) != std::wstring::npos) && + (GetSkin() == (*i)->GetSkin())) + { + (*i)->m_ResultString.clear(); + (*i)->m_DownloadedFile.clear(); + } + } + LeaveCriticalSection(&g_CriticalSection); + } +} diff --git a/Library/MeasureWebParser.h b/Library/MeasureWebParser.h index 709f36fe8..40becd687 100644 --- a/Library/MeasureWebParser.h +++ b/Library/MeasureWebParser.h @@ -1,73 +1,73 @@ -/* Copyright (C) 2015 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#ifndef RM_LIBRARY_MEASUREWEBPARSER_H_ -#define RM_LIBRARY_MEASUREWEBPARSER_H_ - -#include "Measure.h" - -struct ProxySetting -{ - std::wstring agent; - std::wstring server; - HINTERNET handle; - - ProxySetting() : handle() {} -}; - -class MeasureWebParser : public Measure -{ -public: - MeasureWebParser(Skin* skin, const WCHAR* name); - virtual ~MeasureWebParser(); - - MeasureWebParser(const MeasureWebParser& other) = delete; - MeasureWebParser& operator=(MeasureWebParser other) = delete; - - UINT GetTypeID() override { return TypeID(); } - - const WCHAR* GetStringValue() override; - -protected: - void ReadOptions(ConfigParser& parser, const WCHAR* section) override; - void UpdateValue() override; - void Command(const std::wstring& command) override; - -private: - static unsigned __stdcall NetworkThreadProc(void* pParam); - static unsigned __stdcall NetworkDownloadThreadProc(void* pParam); - void ParseData(const BYTE* rawData, DWORD rawSize, bool utf16Data = false); - - std::wstring m_Url; - std::wstring m_RegExp; - std::wstring m_ResultString; - std::wstring m_ErrorString; - std::wstring m_FinishAction; - std::wstring m_OnRegExpErrAction; - std::wstring m_OnConnectErrAction; - std::wstring m_OnDownloadErrAction; - std::wstring m_DownloadFolder; - std::wstring m_DownloadFile; - std::wstring m_DownloadedFile; - std::wstring m_DebugFileLocation; - std::wstring m_Headers; - ProxySetting m_Proxy; - HANDLE m_ThreadHandle; - HANDLE m_DlThreadHandle; - int m_Codepage; - int m_StringIndex; - int m_StringIndex2; - int m_DecodeCharacterReference; - int m_Debug; - UINT m_UpdateRate; - UINT m_UpdateCounter; - bool m_Download; - bool m_ForceReload; - bool m_LogSubstringErrors; -}; - -#endif +/* Copyright (C) 2015 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#ifndef RM_LIBRARY_MEASUREWEBPARSER_H_ +#define RM_LIBRARY_MEASUREWEBPARSER_H_ + +#include "Measure.h" + +struct ProxySetting +{ + std::wstring agent; + std::wstring server; + HINTERNET handle; + + ProxySetting() : handle() {} +}; + +class MeasureWebParser : public Measure +{ +public: + MeasureWebParser(Skin* skin, const WCHAR* name); + virtual ~MeasureWebParser(); + + MeasureWebParser(const MeasureWebParser& other) = delete; + MeasureWebParser& operator=(MeasureWebParser other) = delete; + + UINT GetTypeID() override { return TypeID(); } + + const WCHAR* GetStringValue() override; + +protected: + void ReadOptions(ConfigParser& parser, const WCHAR* section) override; + void UpdateValue() override; + void Command(const std::wstring& command) override; + +private: + static unsigned __stdcall NetworkThreadProc(void* pParam); + static unsigned __stdcall NetworkDownloadThreadProc(void* pParam); + void ParseData(const BYTE* rawData, DWORD rawSize, bool utf16Data = false); + + std::wstring m_Url; + std::wstring m_RegExp; + std::wstring m_ResultString; + std::wstring m_ErrorString; + std::wstring m_FinishAction; + std::wstring m_OnRegExpErrAction; + std::wstring m_OnConnectErrAction; + std::wstring m_OnDownloadErrAction; + std::wstring m_DownloadFolder; + std::wstring m_DownloadFile; + std::wstring m_DownloadedFile; + std::wstring m_DebugFileLocation; + std::wstring m_Headers; + ProxySetting m_Proxy; + HANDLE m_ThreadHandle; + HANDLE m_DlThreadHandle; + int m_Codepage; + int m_StringIndex; + int m_StringIndex2; + int m_DecodeCharacterReference; + int m_Debug; + UINT m_UpdateRate; + UINT m_UpdateCounter; + bool m_Download; + bool m_ForceReload; + bool m_LogSubstringErrors; +}; + +#endif diff --git a/Library/Util.cpp b/Library/Util.cpp index 99291a7dd..298a17282 100644 --- a/Library/Util.cpp +++ b/Library/Util.cpp @@ -1,90 +1,90 @@ -/* Copyright (C) 2002 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#include "StdAfx.h" -#include "Util.h" -#include "Rainmeter.h" -#include "DialogAbout.h" -#include "System.h" - -UINT GetUniqueID() -{ - static UINT id = 0; - return id++; -} - -WCHAR* GetString(UINT id) -{ - LPWSTR pData; - int len = LoadString(GetRainmeter().GetResourceInstance(), id, (LPWSTR)&pData, 0); - return len ? pData : L""; -} - -std::wstring GetFormattedString(UINT id, ...) -{ - LPWSTR pBuffer = nullptr; - va_list args = nullptr; - va_start(args, id); - - DWORD len = FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, - GetString(id), - 0, - 0, - (LPWSTR)&pBuffer, - 0, - &args); - - va_end(args); - - std::wstring tmpSz(len ? pBuffer : L"", len); - if (pBuffer) LocalFree(pBuffer); - return tmpSz; -} - -HICON GetIcon(UINT id, bool large) -{ - HINSTANCE hExe = GetModuleHandle(nullptr); - HINSTANCE hComctl = GetModuleHandle(L"Comctl32"); - if (hComctl) - { - // Try LoadIconMetric for better quality with high DPI - auto loadIconMetric = (decltype(LoadIconMetric)*)GetProcAddress(hComctl, "LoadIconMetric"); - if (loadIconMetric) - { - HICON icon; - HRESULT hr = loadIconMetric(hExe, MAKEINTRESOURCE(id), large ? LIM_LARGE : LIM_SMALL, &icon); - if (SUCCEEDED(hr)) - { - return icon; - } - } - } - - return (HICON)LoadImage( - hExe, - MAKEINTRESOURCE(id), - IMAGE_ICON, - GetSystemMetrics(large ? SM_CXICON : SM_CXSMICON), - GetSystemMetrics(large ? SM_CYICON : SM_CYSMICON), - LR_SHARED); -} - -HICON GetIconBySize(UINT id, int size) -{ - return (HICON)LoadImage( - GetModuleHandle(nullptr), - MAKEINTRESOURCE(id), - IMAGE_ICON, - size, - size, - LR_SHARED); -} - -void RmNullCRTInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) -{ - // Do nothing. -} +/* Copyright (C) 2002 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#include "StdAfx.h" +#include "Util.h" +#include "Rainmeter.h" +#include "DialogAbout.h" +#include "System.h" + +UINT GetUniqueID() +{ + static UINT id = 0; + return id++; +} + +WCHAR* GetString(UINT id) +{ + LPWSTR pData; + int len = LoadString(GetRainmeter().GetResourceInstance(), id, (LPWSTR)&pData, 0); + return len ? pData : L""; +} + +std::wstring GetFormattedString(UINT id, ...) +{ + LPWSTR pBuffer = nullptr; + va_list args = nullptr; + va_start(args, id); + + DWORD len = FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, + GetString(id), + 0, + 0, + (LPWSTR)&pBuffer, + 0, + &args); + + va_end(args); + + std::wstring tmpSz(len ? pBuffer : L"", len); + if (pBuffer) LocalFree(pBuffer); + return tmpSz; +} + +HICON GetIcon(UINT id, bool large) +{ + HINSTANCE hExe = GetModuleHandle(nullptr); + HINSTANCE hComctl = GetModuleHandle(L"Comctl32"); + if (hComctl) + { + // Try LoadIconMetric for better quality with high DPI + auto loadIconMetric = (decltype(LoadIconMetric)*)GetProcAddress(hComctl, "LoadIconMetric"); + if (loadIconMetric) + { + HICON icon; + HRESULT hr = loadIconMetric(hExe, MAKEINTRESOURCE(id), large ? LIM_LARGE : LIM_SMALL, &icon); + if (SUCCEEDED(hr)) + { + return icon; + } + } + } + + return (HICON)LoadImage( + hExe, + MAKEINTRESOURCE(id), + IMAGE_ICON, + GetSystemMetrics(large ? SM_CXICON : SM_CXSMICON), + GetSystemMetrics(large ? SM_CYICON : SM_CYSMICON), + LR_SHARED); +} + +HICON GetIconBySize(UINT id, int size) +{ + return (HICON)LoadImage( + GetModuleHandle(nullptr), + MAKEINTRESOURCE(id), + IMAGE_ICON, + size, + size, + LR_SHARED); +} + +void RmNullCRTInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) +{ + // Do nothing. +} diff --git a/Library/Util.h b/Library/Util.h index b8e67e8c1..d32eacb1c 100644 --- a/Library/Util.h +++ b/Library/Util.h @@ -1,29 +1,29 @@ -/* Copyright (C) 2002 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#ifndef RM_LIBRARY_UTIL_H_ -#define RM_LIBRARY_UTIL_H_ - -#include -#include -#include -#include "../Common/StringUtil.h" - -UINT GetUniqueID(); - -template -UINT TypeID() { static UINT id = GetUniqueID(); return id; } - -WCHAR* GetString(UINT id); -std::wstring GetFormattedString(UINT id, ...); - -HICON GetIcon(UINT id, bool large = false); -HICON GetIconBySize(UINT id, int size); - -void RmNullCRTInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved); - -#endif +/* Copyright (C) 2002 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#ifndef RM_LIBRARY_UTIL_H_ +#define RM_LIBRARY_UTIL_H_ + +#include +#include +#include +#include "../Common/StringUtil.h" + +UINT GetUniqueID(); + +template +UINT TypeID() { static UINT id = GetUniqueID(); return id; } + +WCHAR* GetString(UINT id); +std::wstring GetFormattedString(UINT id, ...); + +HICON GetIcon(UINT id, bool large = false); +HICON GetIconBySize(UINT id, int size); + +void RmNullCRTInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved); + +#endif diff --git a/Plugins/PluginAudioLevel/PluginAudioLevel.cpp b/Plugins/PluginAudioLevel/PluginAudioLevel.cpp index 90f5f5258..a6ca56f91 100644 --- a/Plugins/PluginAudioLevel/PluginAudioLevel.cpp +++ b/Plugins/PluginAudioLevel/PluginAudioLevel.cpp @@ -1,431 +1,431 @@ -/* Copyright (C) 2014 Rainmeter Project Developers - * - * This Source Code Form is subject to the terms of the GNU General Public - * License; either version 2 of the License, or (at your option) any later - * version. If a copy of the GPL was not distributed with this file, You can - * obtain one at . */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "../API/RainmeterAPI.h" - -#include "kiss_fft130/kiss_fftr.h" - -// Overview: Audio level measurement from the Window Core Audio API -// See: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370800%28v=vs.85%29.aspx - -// Sample skin: -/* - [mAudio_Raw] - Measure=Plugin - Plugin=AudioLevel.dll - Port=Output - - [mAudio_RMS_L] - Measure=Plugin - Plugin=AudioLevel.dll - Parent=mAudio_Raw - Type=RMS - Channel=L - - [mAudio_RMS_R] - Measure=Plugin - Plugin=AudioLevel.dll - Parent=mAudio_Raw - Type=RMS - Channel=R -*/ - -// REFERENCE_TIME time units per second and per millisecond -#define WINDOWS_BUG_WORKAROUND 1 -#define REFTIMES_PER_SEC 10000000 -#define TWOPI (2 * 3.14159265358979323846) -#define EXIT_ON_ERROR(hres) if (FAILED(hres)) { goto Exit; } -#define SAFE_RELEASE(p) if ((p) != NULL) { (p)->Release(); (p) = NULL; } -#define CLAMP01(x) max(0.0, min(1.0, (x))) - -#define EMPTY_TIMEOUT 0.500 -#define DEVICE_TIMEOUT 1.500 -#define QUERY_TIMEOUT (1.0 / 60) - -struct Measure -{ - enum Port - { - PORT_OUTPUT, - PORT_INPUT, - }; - - enum Channel - { - CHANNEL_FL, - CHANNEL_FR, - CHANNEL_C, - CHANNEL_LFE, - CHANNEL_BL, - CHANNEL_BR, - CHANNEL_SL, - CHANNEL_SR, - MAX_CHANNELS, - CHANNEL_SUM = MAX_CHANNELS - }; - - enum Type - { - TYPE_RMS, - TYPE_PEAK, - TYPE_FFT, - TYPE_BAND, - TYPE_FFTFREQ, - TYPE_BANDFREQ, - TYPE_FORMAT, - TYPE_DEV_STATUS, - TYPE_DEV_NAME, - TYPE_DEV_ID, - TYPE_DEV_LIST, - // ... // - NUM_TYPES - }; - - enum Format - { - FMT_INVALID, - FMT_PCM_S16, - FMT_PCM_F32, - // ... // - NUM_FORMATS - }; - - struct BandInfo - { - float freq; - float x; - }; - - Port m_port; // port specifier (parsed from options) - Channel m_channel; // channel specifier (parsed from options) - Type m_type; // data type specifier (parsed from options) - Format m_format; // format specifier (detected in init) - int m_envRMS[2]; // RMS attack/decay times in ms (parsed from options) - int m_envPeak[2]; // peak attack/decay times in ms (parsed from options) - int m_envFFT[2]; // FFT attack/decay times in ms (parsed from options) - int m_fftSize; // size of FFT (parsed from options) - int m_fftOverlap; // number of samples between FFT calculations - int m_fftIdx; // FFT index to retrieve (parsed from options) - int m_nBands; // number of frequency bands (parsed from options) - int m_bandIdx; // band index to retrieve (parsed from options) - double m_gainRMS; // RMS gain (parsed from options) - double m_gainPeak; // peak gain (parsed from options) - double m_freqMin; // min freq for band measurement - double m_freqMax; // max freq for band measurement - double m_sensitivity; // dB range for FFT/Band return values (parsed from options) - Measure* m_parent; // parent measure, if any - void* m_skin; // skin pointer - LPCWSTR m_rmName; // measure name - IMMDeviceEnumerator* m_enum; // audio endpoint enumerator - IMMDevice* m_dev; // audio endpoint device - WAVEFORMATEX* m_wfx; // audio format info - IAudioClient* m_clAudio; // audio client instance - IAudioCaptureClient* m_clCapture; // capture client instance -#if (WINDOWS_BUG_WORKAROUND) - IAudioClient* m_clBugAudio; // audio client for dummy silent channel - IAudioRenderClient* m_clBugRender; // render client for dummy silent channel -#endif - WCHAR m_reqID[64]; // requested device ID (parsed from options) - WCHAR m_devName[64]; // device friendly name (detected in init) - float m_kRMS[2]; // RMS attack/decay filter constants - float m_kPeak[2]; // peak attack/decay filter constants - float m_kFFT[2]; // FFT attack/decay filter constants - double m_rms[MAX_CHANNELS]; // current RMS levels - double m_peak[MAX_CHANNELS]; // current peak levels - double m_pcMult; // performance counter inv frequency - LARGE_INTEGER m_pcFill; // performance counter on last full buffer - LARGE_INTEGER m_pcPoll; // performance counter on last device poll - kiss_fftr_cfg m_fftCfg[MAX_CHANNELS]; // FFT states for each channel - float* m_fftIn[MAX_CHANNELS]; // buffer for each channel's FFT input - float* m_fftOut[MAX_CHANNELS]; // buffer for each channel's FFT output - float* m_fftKWdw; // window function coefficients - float* m_fftTmpIn; // temp FFT processing buffer - kiss_fft_cpx* m_fftTmpOut; // temp FFT processing buffer - int m_fftBufW; // write index for input ring buffers - int m_fftBufP; // decremental counter - process FFT at zero - float* m_bandFreq; // buffer of band max frequencies - float* m_bandOut[MAX_CHANNELS]; // buffer of band values - - Measure() : - m_port(PORT_OUTPUT), - m_channel(CHANNEL_SUM), - m_type(TYPE_RMS), - m_format(FMT_INVALID), - m_fftSize(0), - m_fftOverlap(0), - m_fftIdx(-1), - m_nBands(0), - m_bandIdx(-1), - m_gainRMS(1.0), - m_gainPeak(1.0), - m_freqMin(20.0), - m_freqMax(20000.0), - m_sensitivity(35.0), - m_parent(NULL), - m_skin(NULL), - m_rmName(NULL), - m_enum(NULL), - m_dev(NULL), - m_wfx(NULL), - m_clAudio(NULL), - m_clCapture(NULL), -#if (WINDOWS_BUG_WORKAROUND) - m_clBugAudio(NULL), - m_clBugRender(NULL), -#endif - m_fftKWdw(NULL), - m_fftTmpIn(NULL), - m_fftTmpOut(NULL), - m_fftBufW(0), - m_fftBufP(0), - m_bandFreq(NULL) - { - m_envRMS[0] = 300; - m_envRMS[1] = 300; - m_envPeak[0] = 50; - m_envPeak[1] = 2500; - m_envFFT[0] = 300; - m_envFFT[1] = 300; - m_reqID[0] = '\0'; - m_devName[0] = '\0'; - m_kRMS[0] = 0.0f; - m_kRMS[1] = 0.0f; - m_kPeak[0] = 0.0f; - m_kPeak[1] = 0.0f; - m_kFFT[0] = 0.0f; - m_kFFT[1] = 0.0f; - - for (int iChan = 0; iChan < MAX_CHANNELS; ++iChan) - { - m_rms[iChan] = 0.0; - m_peak[iChan] = 0.0; - m_fftCfg[iChan] = NULL; - m_fftIn[iChan] = NULL; - m_fftOut[iChan] = NULL; - m_bandOut[iChan] = NULL; - } - - LARGE_INTEGER pcFreq; - QueryPerformanceFrequency(&pcFreq); - m_pcMult = 1.0 / (double)pcFreq.QuadPart; - } - - HRESULT DeviceInit(); - void DeviceRelease(); -}; - -const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); -const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); -const IID IID_IAudioClient = __uuidof(IAudioClient); -const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); -const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient); - -std::vector s_parents; - -/** - * Create and initialize a measure instance. Creates WASAPI loopback - * device if not a child measure. - * - * @param[out] data Pointer address in which to return measure instance. - * @param[in] rm Rainmeter context. - */ -PLUGIN_EXPORT void Initialize (void** data, void* rm) -{ - Measure* m = new Measure; - m->m_skin = RmGetSkin(rm); - m->m_rmName = RmGetMeasureName(rm); - *data = m; - - // parse parent specifier, if appropriate - LPCWSTR parentName = RmReadString(rm, L"Parent", L""); - if (*parentName) - { - // match parent using measure name and skin handle - std::vector::const_iterator iter = s_parents.begin(); - for ( ; iter != s_parents.end(); ++iter) - { - if (_wcsicmp((*iter)->m_rmName, parentName) == 0 && - (*iter)->m_skin == m->m_skin && - !(*iter)->m_parent) - { - m->m_parent = (*iter); - return; - } - } - - RmLogF(rm, LOG_ERROR, L"Couldn't find Parent measure '%s'.", parentName); - } - - // this is a parent measure - add it to the global list - s_parents.push_back(m); - - // parse port specifier - LPCWSTR port = RmReadString(rm, L"Port", L""); - if (port && *port) - { - if (_wcsicmp(port, L"Output") == 0) - { - m->m_port = Measure::PORT_OUTPUT; - } - else if (_wcsicmp(port, L"Input") == 0) - { - m->m_port = Measure::PORT_INPUT; - } - else - { - RmLogF(rm, LOG_ERROR, L"Invalid Port '%s', must be one of: Output or Input.", port); - } - } - - // parse requested device ID (optional) - LPCWSTR reqID = RmReadString(rm, L"ID", L""); - if (reqID) - { - _snwprintf_s(m->m_reqID, _TRUNCATE, L"%s", reqID); - } - - // initialize FFT data - m->m_fftSize = RmReadInt(rm, L"FFTSize", m->m_fftSize); - if (m->m_fftSize < 0 || m->m_fftSize & 1) - { - RmLogF(rm, LOG_ERROR, L"Invalid FFTSize %ld: must be an even integer >= 0. (powers of 2 work best)", m->m_fftSize); - m->m_fftSize = 0; - } - - if (m->m_fftSize) - { - m->m_fftOverlap = RmReadInt(rm, L"FFTOverlap", m->m_fftOverlap); - if (m->m_fftOverlap < 0 || m->m_fftOverlap >= m->m_fftSize) - { - RmLogF(rm, LOG_ERROR, L"Invalid FFTOverlap %ld: must be an integer between 0 and FFTSize(%ld).", m->m_fftOverlap, m->m_fftSize); - m->m_fftOverlap = 0; - } - } - - // initialize frequency bands - m->m_nBands = RmReadInt(rm, L"Bands", m->m_nBands); - if (m->m_nBands < 0) - { - RmLogF(rm, LOG_ERROR, L"AudioLevel.dll: Invalid Bands %ld: must be an integer >= 0.", m->m_nBands); - m->m_nBands = 0; - } - - m->m_freqMin = max(0.0, RmReadDouble(rm, L"FreqMin", m->m_freqMin)); - m->m_freqMax = max(0.0, RmReadDouble(rm, L"FreqMax", m->m_freqMax)); - - // initialize the watchdog timer - QueryPerformanceCounter(&m->m_pcPoll); - - // create the enumerator - if (CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&m->m_enum) == S_OK) - { - // init the device (ok if it fails - it'll keep checking during Update) - m->DeviceInit(); - return; - } - - SAFE_RELEASE(m->m_enum); -} - - -/** - * Destroy the measure instance. - * - * @param[in] data Measure instance pointer. - */ -PLUGIN_EXPORT void Finalize (void* data) -{ - Measure* m = (Measure*)data; - - m->DeviceRelease(); - SAFE_RELEASE(m->m_enum); - - if (!m->m_parent) - { - std::vector::iterator iter = std::find(s_parents.begin(), s_parents.end(), m); - s_parents.erase(iter); - } - - delete m; -} - - -/** - * (Re-)parse parameters from .ini file. - * - * @param[in] data Measure instance pointer. - * @param[in] rm Rainmeter context. - * @param[out] maxValue ? - */ -PLUGIN_EXPORT void Reload (void* data, void* rm, double* maxValue) -{ - static const LPCWSTR s_typeName[Measure::NUM_TYPES] = - { - L"RMS", // TYPE_RMS - L"Peak", // TYPE_PEAK - L"FFT", // TYPE_FFT - L"Band", // TYPE_BAND - L"FFTFreq", // TYPE_FFTFREQ - L"BandFreq", // TYPE_BANDFREQ - L"Format", // TYPE_FORMAT - L"DeviceStatus", // TYPE_DEV_STATUS - L"DeviceName", // TYPE_DEV_NAME - L"DeviceID", // TYPE_DEV_ID - L"DeviceList", // TYPE_DEV_LIST - }; - - static const LPCWSTR s_chanName[Measure::CHANNEL_SUM + 1][3] = - { - { L"L", L"FL", L"0", }, // CHANNEL_FL - { L"R", L"FR", L"1", }, // CHANNEL_FR - { L"C", L"", L"2", }, // CHANNEL_C - { L"LFE", L"Sub", L"3", }, // CHANNEL_LFE - { L"BL", L"", L"4", }, // CHANNEL_BL - { L"BR", L"", L"5", }, // CHANNEL_BR - { L"SL", L"", L"6", }, // CHANNEL_SL - { L"SR", L"", L"7", }, // CHANNEL_SR - { L"Sum", L"Avg", L"", }, // CHANNEL_SUM - }; - - Measure* m = (Measure*)data; - - // parse channel specifier - LPCWSTR channel = RmReadString(rm, L"Channel", L""); - if (*channel) - { - bool found = false; - for (int iChan = 0; iChan <= Measure::CHANNEL_SUM && !found; ++iChan) - { - for (int j = 0; j < 3; ++j) - { - if (_wcsicmp(channel, s_chanName[iChan][j]) == 0) - { - m->m_channel = (Measure::Channel)iChan; - found = true; - break; - } - } - } - - if (!found) - { +/* Copyright (C) 2014 Rainmeter Project Developers + * + * This Source Code Form is subject to the terms of the GNU General Public + * License; either version 2 of the License, or (at your option) any later + * version. If a copy of the GPL was not distributed with this file, You can + * obtain one at . */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../API/RainmeterAPI.h" + +#include "kiss_fft130/kiss_fftr.h" + +// Overview: Audio level measurement from the Window Core Audio API +// See: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370800%28v=vs.85%29.aspx + +// Sample skin: +/* + [mAudio_Raw] + Measure=Plugin + Plugin=AudioLevel.dll + Port=Output + + [mAudio_RMS_L] + Measure=Plugin + Plugin=AudioLevel.dll + Parent=mAudio_Raw + Type=RMS + Channel=L + + [mAudio_RMS_R] + Measure=Plugin + Plugin=AudioLevel.dll + Parent=mAudio_Raw + Type=RMS + Channel=R +*/ + +// REFERENCE_TIME time units per second and per millisecond +#define WINDOWS_BUG_WORKAROUND 1 +#define REFTIMES_PER_SEC 10000000 +#define TWOPI (2 * 3.14159265358979323846) +#define EXIT_ON_ERROR(hres) if (FAILED(hres)) { goto Exit; } +#define SAFE_RELEASE(p) if ((p) != NULL) { (p)->Release(); (p) = NULL; } +#define CLAMP01(x) max(0.0, min(1.0, (x))) + +#define EMPTY_TIMEOUT 0.500 +#define DEVICE_TIMEOUT 1.500 +#define QUERY_TIMEOUT (1.0 / 60) + +struct Measure +{ + enum Port + { + PORT_OUTPUT, + PORT_INPUT, + }; + + enum Channel + { + CHANNEL_FL, + CHANNEL_FR, + CHANNEL_C, + CHANNEL_LFE, + CHANNEL_BL, + CHANNEL_BR, + CHANNEL_SL, + CHANNEL_SR, + MAX_CHANNELS, + CHANNEL_SUM = MAX_CHANNELS + }; + + enum Type + { + TYPE_RMS, + TYPE_PEAK, + TYPE_FFT, + TYPE_BAND, + TYPE_FFTFREQ, + TYPE_BANDFREQ, + TYPE_FORMAT, + TYPE_DEV_STATUS, + TYPE_DEV_NAME, + TYPE_DEV_ID, + TYPE_DEV_LIST, + // ... // + NUM_TYPES + }; + + enum Format + { + FMT_INVALID, + FMT_PCM_S16, + FMT_PCM_F32, + // ... // + NUM_FORMATS + }; + + struct BandInfo + { + float freq; + float x; + }; + + Port m_port; // port specifier (parsed from options) + Channel m_channel; // channel specifier (parsed from options) + Type m_type; // data type specifier (parsed from options) + Format m_format; // format specifier (detected in init) + int m_envRMS[2]; // RMS attack/decay times in ms (parsed from options) + int m_envPeak[2]; // peak attack/decay times in ms (parsed from options) + int m_envFFT[2]; // FFT attack/decay times in ms (parsed from options) + int m_fftSize; // size of FFT (parsed from options) + int m_fftOverlap; // number of samples between FFT calculations + int m_fftIdx; // FFT index to retrieve (parsed from options) + int m_nBands; // number of frequency bands (parsed from options) + int m_bandIdx; // band index to retrieve (parsed from options) + double m_gainRMS; // RMS gain (parsed from options) + double m_gainPeak; // peak gain (parsed from options) + double m_freqMin; // min freq for band measurement + double m_freqMax; // max freq for band measurement + double m_sensitivity; // dB range for FFT/Band return values (parsed from options) + Measure* m_parent; // parent measure, if any + void* m_skin; // skin pointer + LPCWSTR m_rmName; // measure name + IMMDeviceEnumerator* m_enum; // audio endpoint enumerator + IMMDevice* m_dev; // audio endpoint device + WAVEFORMATEX* m_wfx; // audio format info + IAudioClient* m_clAudio; // audio client instance + IAudioCaptureClient* m_clCapture; // capture client instance +#if (WINDOWS_BUG_WORKAROUND) + IAudioClient* m_clBugAudio; // audio client for dummy silent channel + IAudioRenderClient* m_clBugRender; // render client for dummy silent channel +#endif + WCHAR m_reqID[64]; // requested device ID (parsed from options) + WCHAR m_devName[64]; // device friendly name (detected in init) + float m_kRMS[2]; // RMS attack/decay filter constants + float m_kPeak[2]; // peak attack/decay filter constants + float m_kFFT[2]; // FFT attack/decay filter constants + double m_rms[MAX_CHANNELS]; // current RMS levels + double m_peak[MAX_CHANNELS]; // current peak levels + double m_pcMult; // performance counter inv frequency + LARGE_INTEGER m_pcFill; // performance counter on last full buffer + LARGE_INTEGER m_pcPoll; // performance counter on last device poll + kiss_fftr_cfg m_fftCfg[MAX_CHANNELS]; // FFT states for each channel + float* m_fftIn[MAX_CHANNELS]; // buffer for each channel's FFT input + float* m_fftOut[MAX_CHANNELS]; // buffer for each channel's FFT output + float* m_fftKWdw; // window function coefficients + float* m_fftTmpIn; // temp FFT processing buffer + kiss_fft_cpx* m_fftTmpOut; // temp FFT processing buffer + int m_fftBufW; // write index for input ring buffers + int m_fftBufP; // decremental counter - process FFT at zero + float* m_bandFreq; // buffer of band max frequencies + float* m_bandOut[MAX_CHANNELS]; // buffer of band values + + Measure() : + m_port(PORT_OUTPUT), + m_channel(CHANNEL_SUM), + m_type(TYPE_RMS), + m_format(FMT_INVALID), + m_fftSize(0), + m_fftOverlap(0), + m_fftIdx(-1), + m_nBands(0), + m_bandIdx(-1), + m_gainRMS(1.0), + m_gainPeak(1.0), + m_freqMin(20.0), + m_freqMax(20000.0), + m_sensitivity(35.0), + m_parent(NULL), + m_skin(NULL), + m_rmName(NULL), + m_enum(NULL), + m_dev(NULL), + m_wfx(NULL), + m_clAudio(NULL), + m_clCapture(NULL), +#if (WINDOWS_BUG_WORKAROUND) + m_clBugAudio(NULL), + m_clBugRender(NULL), +#endif + m_fftKWdw(NULL), + m_fftTmpIn(NULL), + m_fftTmpOut(NULL), + m_fftBufW(0), + m_fftBufP(0), + m_bandFreq(NULL) + { + m_envRMS[0] = 300; + m_envRMS[1] = 300; + m_envPeak[0] = 50; + m_envPeak[1] = 2500; + m_envFFT[0] = 300; + m_envFFT[1] = 300; + m_reqID[0] = '\0'; + m_devName[0] = '\0'; + m_kRMS[0] = 0.0f; + m_kRMS[1] = 0.0f; + m_kPeak[0] = 0.0f; + m_kPeak[1] = 0.0f; + m_kFFT[0] = 0.0f; + m_kFFT[1] = 0.0f; + + for (int iChan = 0; iChan < MAX_CHANNELS; ++iChan) + { + m_rms[iChan] = 0.0; + m_peak[iChan] = 0.0; + m_fftCfg[iChan] = NULL; + m_fftIn[iChan] = NULL; + m_fftOut[iChan] = NULL; + m_bandOut[iChan] = NULL; + } + + LARGE_INTEGER pcFreq; + QueryPerformanceFrequency(&pcFreq); + m_pcMult = 1.0 / (double)pcFreq.QuadPart; + } + + HRESULT DeviceInit(); + void DeviceRelease(); +}; + +const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); +const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); +const IID IID_IAudioClient = __uuidof(IAudioClient); +const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); +const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient); + +std::vector s_parents; + +/** + * Create and initialize a measure instance. Creates WASAPI loopback + * device if not a child measure. + * + * @param[out] data Pointer address in which to return measure instance. + * @param[in] rm Rainmeter context. + */ +PLUGIN_EXPORT void Initialize (void** data, void* rm) +{ + Measure* m = new Measure; + m->m_skin = RmGetSkin(rm); + m->m_rmName = RmGetMeasureName(rm); + *data = m; + + // parse parent specifier, if appropriate + LPCWSTR parentName = RmReadString(rm, L"Parent", L""); + if (*parentName) + { + // match parent using measure name and skin handle + std::vector::const_iterator iter = s_parents.begin(); + for ( ; iter != s_parents.end(); ++iter) + { + if (_wcsicmp((*iter)->m_rmName, parentName) == 0 && + (*iter)->m_skin == m->m_skin && + !(*iter)->m_parent) + { + m->m_parent = (*iter); + return; + } + } + + RmLogF(rm, LOG_ERROR, L"Couldn't find Parent measure '%s'.", parentName); + } + + // this is a parent measure - add it to the global list + s_parents.push_back(m); + + // parse port specifier + LPCWSTR port = RmReadString(rm, L"Port", L""); + if (port && *port) + { + if (_wcsicmp(port, L"Output") == 0) + { + m->m_port = Measure::PORT_OUTPUT; + } + else if (_wcsicmp(port, L"Input") == 0) + { + m->m_port = Measure::PORT_INPUT; + } + else + { + RmLogF(rm, LOG_ERROR, L"Invalid Port '%s', must be one of: Output or Input.", port); + } + } + + // parse requested device ID (optional) + LPCWSTR reqID = RmReadString(rm, L"ID", L""); + if (reqID) + { + _snwprintf_s(m->m_reqID, _TRUNCATE, L"%s", reqID); + } + + // initialize FFT data + m->m_fftSize = RmReadInt(rm, L"FFTSize", m->m_fftSize); + if (m->m_fftSize < 0 || m->m_fftSize & 1) + { + RmLogF(rm, LOG_ERROR, L"Invalid FFTSize %ld: must be an even integer >= 0. (powers of 2 work best)", m->m_fftSize); + m->m_fftSize = 0; + } + + if (m->m_fftSize) + { + m->m_fftOverlap = RmReadInt(rm, L"FFTOverlap", m->m_fftOverlap); + if (m->m_fftOverlap < 0 || m->m_fftOverlap >= m->m_fftSize) + { + RmLogF(rm, LOG_ERROR, L"Invalid FFTOverlap %ld: must be an integer between 0 and FFTSize(%ld).", m->m_fftOverlap, m->m_fftSize); + m->m_fftOverlap = 0; + } + } + + // initialize frequency bands + m->m_nBands = RmReadInt(rm, L"Bands", m->m_nBands); + if (m->m_nBands < 0) + { + RmLogF(rm, LOG_ERROR, L"AudioLevel.dll: Invalid Bands %ld: must be an integer >= 0.", m->m_nBands); + m->m_nBands = 0; + } + + m->m_freqMin = max(0.0, RmReadDouble(rm, L"FreqMin", m->m_freqMin)); + m->m_freqMax = max(0.0, RmReadDouble(rm, L"FreqMax", m->m_freqMax)); + + // initialize the watchdog timer + QueryPerformanceCounter(&m->m_pcPoll); + + // create the enumerator + if (CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&m->m_enum) == S_OK) + { + // init the device (ok if it fails - it'll keep checking during Update) + m->DeviceInit(); + return; + } + + SAFE_RELEASE(m->m_enum); +} + + +/** + * Destroy the measure instance. + * + * @param[in] data Measure instance pointer. + */ +PLUGIN_EXPORT void Finalize (void* data) +{ + Measure* m = (Measure*)data; + + m->DeviceRelease(); + SAFE_RELEASE(m->m_enum); + + if (!m->m_parent) + { + std::vector::iterator iter = std::find(s_parents.begin(), s_parents.end(), m); + s_parents.erase(iter); + } + + delete m; +} + + +/** + * (Re-)parse parameters from .ini file. + * + * @param[in] data Measure instance pointer. + * @param[in] rm Rainmeter context. + * @param[out] maxValue ? + */ +PLUGIN_EXPORT void Reload (void* data, void* rm, double* maxValue) +{ + static const LPCWSTR s_typeName[Measure::NUM_TYPES] = + { + L"RMS", // TYPE_RMS + L"Peak", // TYPE_PEAK + L"FFT", // TYPE_FFT + L"Band", // TYPE_BAND + L"FFTFreq", // TYPE_FFTFREQ + L"BandFreq", // TYPE_BANDFREQ + L"Format", // TYPE_FORMAT + L"DeviceStatus", // TYPE_DEV_STATUS + L"DeviceName", // TYPE_DEV_NAME + L"DeviceID", // TYPE_DEV_ID + L"DeviceList", // TYPE_DEV_LIST + }; + + static const LPCWSTR s_chanName[Measure::CHANNEL_SUM + 1][3] = + { + { L"L", L"FL", L"0", }, // CHANNEL_FL + { L"R", L"FR", L"1", }, // CHANNEL_FR + { L"C", L"", L"2", }, // CHANNEL_C + { L"LFE", L"Sub", L"3", }, // CHANNEL_LFE + { L"BL", L"", L"4", }, // CHANNEL_BL + { L"BR", L"", L"5", }, // CHANNEL_BR + { L"SL", L"", L"6", }, // CHANNEL_SL + { L"SR", L"", L"7", }, // CHANNEL_SR + { L"Sum", L"Avg", L"", }, // CHANNEL_SUM + }; + + Measure* m = (Measure*)data; + + // parse channel specifier + LPCWSTR channel = RmReadString(rm, L"Channel", L""); + if (*channel) + { + bool found = false; + for (int iChan = 0; iChan <= Measure::CHANNEL_SUM && !found; ++iChan) + { + for (int j = 0; j < 3; ++j) + { + if (_wcsicmp(channel, s_chanName[iChan][j]) == 0) + { + m->m_channel = (Measure::Channel)iChan; + found = true; + break; + } + } + } + + if (!found) + { std::wstring chanNames = s_chanName[0][0]; for (size_t i = 1; i <= Measure::CHANNEL_SUM; ++i) { @@ -433,27 +433,27 @@ PLUGIN_EXPORT void Reload (void* data, void* rm, double* maxValue) chanNames += s_chanName[i][0]; } - RmLogF(rm, LOG_ERROR, L"Invalid Channel: '%s', must be a number between 0 and %i, or one of: %s", - channel, Measure::MAX_CHANNELS - 1, chanNames.c_str()); - } - } - - // parse data type - LPCWSTR type = RmReadString(rm, L"Type", L""); - if (*type) - { - int iType; - for (iType = 0; iType < Measure::NUM_TYPES; ++iType) - { - if (_wcsicmp(type, s_typeName[iType]) == 0) - { - m->m_type = (Measure::Type)iType; - break; - } - } - - if (!(iType < Measure::NUM_TYPES)) - { + RmLogF(rm, LOG_ERROR, L"Invalid Channel: '%s', must be a number between 0 and %i, or one of: %s", + channel, Measure::MAX_CHANNELS - 1, chanNames.c_str()); + } + } + + // parse data type + LPCWSTR type = RmReadString(rm, L"Type", L""); + if (*type) + { + int iType; + for (iType = 0; iType < Measure::NUM_TYPES; ++iType) + { + if (_wcsicmp(type, s_typeName[iType]) == 0) + { + m->m_type = (Measure::Type)iType; + break; + } + } + + if (!(iType < Measure::NUM_TYPES)) + { std::wstring typeNames = s_typeName[0]; for (size_t i = 1; i < Measure::NUM_TYPES; ++i) { @@ -461,825 +461,825 @@ PLUGIN_EXPORT void Reload (void* data, void* rm, double* maxValue) typeNames += s_typeName[i]; } - RmLogF(rm, LOG_ERROR, L"Invalid Type: '%s', must be one of: %s", type, typeNames.c_str()); - } - } - - // parse FFT index request - m->m_fftIdx = max(0, RmReadInt(rm, L"FFTIdx", m->m_fftIdx)); - m->m_fftIdx = m->m_parent ? - min(m->m_parent->m_fftSize / 2, m->m_fftIdx) : - min(m->m_fftSize / 2, m->m_fftIdx); - - // parse band index request - m->m_bandIdx = max(0, RmReadInt(rm, L"BandIdx", m->m_bandIdx)); - m->m_bandIdx = m->m_parent ? - min(m->m_parent->m_nBands, m->m_bandIdx) : - min(m->m_nBands, m->m_bandIdx); - - // parse envelope values on parents only - if (!m->m_parent) - { - // (re)parse envelope values - m->m_envRMS[0] = max(0, RmReadInt(rm, L"RMSAttack", m->m_envRMS[0])); - m->m_envRMS[1] = max(0, RmReadInt(rm, L"RMSDecay", m->m_envRMS[1])); - m->m_envPeak[0] = max(0, RmReadInt(rm, L"PeakAttack", m->m_envPeak[0])); - m->m_envPeak[1] = max(0, RmReadInt(rm, L"PeakDecay", m->m_envPeak[1])); - m->m_envFFT[0] = max(0, RmReadInt(rm, L"FFTAttack", m->m_envFFT[0])); - m->m_envFFT[1] = max(0, RmReadInt(rm, L"FFTDecay", m->m_envFFT[1])); - - // (re)parse gain constants - m->m_gainRMS = max(0.0, RmReadDouble(rm, L"RMSGain", m->m_gainRMS)); - m->m_gainPeak = max(0.0, RmReadDouble(rm, L"PeakGain", m->m_gainPeak)); - m->m_sensitivity = max(1.0, RmReadDouble(rm, L"Sensitivity", m->m_sensitivity)); - - // regenerate filter constants - if (m->m_wfx) - { - const double freq = m->m_wfx->nSamplesPerSec; - m->m_kRMS[0] = (float) exp(log10(0.01) / (freq * (double)m->m_envRMS[0] * 0.001)); - m->m_kRMS[1] = (float) exp(log10(0.01) / (freq * (double)m->m_envRMS[1] * 0.001)); - m->m_kPeak[0] = (float) exp(log10(0.01) / (freq * (double)m->m_envPeak[0] * 0.001)); - m->m_kPeak[1] = (float) exp(log10(0.01) / (freq * (double)m->m_envPeak[1] * 0.001)); - - if (m->m_fftSize) - { - m->m_kFFT[0] = (float) exp(log10(0.01) / (freq / (m->m_fftSize-m->m_fftOverlap) * (double)m->m_envFFT[0] * 0.001)); - m->m_kFFT[1] = (float) exp(log10(0.01) / (freq / (m->m_fftSize-m->m_fftOverlap) * (double)m->m_envFFT[1] * 0.001)); - } - } - } -} - - -/** - * Update the measure. - * - * @param[in] data Measure instance pointer. - * @return Latest value - typically an audio level between 0.0 and 1.0. - */ -PLUGIN_EXPORT double Update (void* data) -{ - Measure* m = (Measure*)data; - Measure* parent = m->m_parent ? m->m_parent : m; - LARGE_INTEGER pcCur; - QueryPerformanceCounter(&pcCur); - - // query the buffer - if (m->m_clCapture && (pcCur.QuadPart - m->m_pcPoll.QuadPart) * m->m_pcMult >= QUERY_TIMEOUT) - { - BYTE* buffer; - UINT32 nFrames; - DWORD flags; - UINT64 pos; - HRESULT hr; - - while ((hr = m->m_clCapture->GetBuffer(&buffer, &nFrames, &flags, &pos, NULL)) == S_OK) - { - // measure RMS and peak levels - float rms[Measure::MAX_CHANNELS]; - float peak[Measure::MAX_CHANNELS]; - for (int iChan = 0; iChan < Measure::MAX_CHANNELS; ++iChan) - { - rms[iChan] = (float)m->m_rms[iChan]; - peak[iChan] = (float)m->m_peak[iChan]; - } - - // loops unrolled for float, 16b and mono, stereo - if (m->m_format == Measure::FMT_PCM_F32) - { - float* s = (float*)buffer; - if (m->m_wfx->nChannels == 1) - { - for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) - { - float xL = (float)*s++; - float sqrL = xL * xL; - float absL = abs(xL); - rms[0] = sqrL + m->m_kRMS[(sqrL < rms[0])] * (rms[0] - sqrL); - peak[0] = absL + m->m_kPeak[(absL < peak[0])] * (peak[0] - absL); - rms[1] = rms[0]; - peak[1] = peak[0]; - } - } - else if (m->m_wfx->nChannels == 2) - { - for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) - { - float xL = (float)*s++; - float xR = (float)*s++; - float sqrL = xL * xL; - float sqrR = xR * xR; - float absL = abs(xL); - float absR = abs(xR); - rms[0] = sqrL + m->m_kRMS[(sqrL < rms[0])] * (rms[0] - sqrL); - rms[1] = sqrR + m->m_kRMS[(sqrR < rms[1])] * (rms[1] - sqrR); - peak[0] = absL + m->m_kPeak[(absL < peak[0])] * (peak[0] - absL); - peak[1] = absR + m->m_kPeak[(absR < peak[1])] * (peak[1] - absR); - } - } - else - { - for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) - { - for (unsigned int iChan = 0; iChan < m->m_wfx->nChannels; ++iChan) - { - float x = (float)*s++; - float sqrX = x * x; - float absX = abs(x); - rms[iChan] = sqrX + m->m_kRMS[(sqrX < rms[iChan])] * (rms[iChan] - sqrX); - peak[iChan] = absX + m->m_kPeak[(absX < peak[iChan])] * (peak[iChan] - absX); - } - } - } - } - else if (m->m_format == Measure::FMT_PCM_S16) - { - INT16* s = (INT16*)buffer; - if (m->m_wfx->nChannels == 1) - { - for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) - { - float xL = (float)*s++ * 1.0f / 0x7fff; - float sqrL = xL * xL; - float absL = abs(xL); - rms[0] = sqrL + m->m_kRMS[(sqrL < rms[0])] * (rms[0] - sqrL); - peak[0] = absL + m->m_kPeak[(absL < peak[0])] * (peak[0] - absL); - rms[1] = rms[0]; - peak[1] = peak[0]; - } - } - else if (m->m_wfx->nChannels == 2) - { - for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) - { - float xL = (float)*s++ * 1.0f / 0x7fff; - float xR = (float)*s++ * 1.0f / 0x7fff; - float sqrL = xL * xL; - float sqrR = xR * xR; - float absL = abs(xL); - float absR = abs(xR); - rms[0] = sqrL + m->m_kRMS[(sqrL < rms[0])] * (rms[0] - sqrL); - rms[1] = sqrR + m->m_kRMS[(sqrR < rms[1])] * (rms[1] - sqrR); - peak[0] = absL + m->m_kPeak[(absL < peak[0])] * (peak[0] - absL); - peak[1] = absR + m->m_kPeak[(absR < peak[1])] * (peak[1] - absR); - } - } - else - { - for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) - { - for (unsigned int iChan = 0; iChan < m->m_wfx->nChannels; ++iChan) - { - float x = (float)*s++ * 1.0f / 0x7fff; - float sqrX = x * x; - float absX = abs(x); - rms[iChan] = sqrX + m->m_kRMS[(sqrX < rms[iChan])] * (rms[iChan] - sqrX); - peak[iChan] = absX + m->m_kPeak[(absX < peak[iChan])] * (peak[iChan] - absX); - } - } - } - } - - for (int iChan = 0; iChan < Measure::MAX_CHANNELS; ++iChan) - { - m->m_rms[iChan] = rms[iChan]; - m->m_peak[iChan] = peak[iChan]; - } - - // process FFTs (optional) - if (m->m_fftSize) - { - float* sF32 = (float*)buffer; - INT16* sI16 = (INT16*)buffer; - const float scalar = (float)(1.0 / sqrt(m->m_fftSize)); - - for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) - { - // fill ring buffers (demux streams) - for (unsigned int iChan = 0; iChan < m->m_wfx->nChannels; ++iChan) - { - (m->m_fftIn[iChan])[m->m_fftBufW] = m->m_format == Measure::FMT_PCM_F32 ? *sF32++ : (float)*sI16++ * 1.0f / 0x7fff; - } - - m->m_fftBufW = (m->m_fftBufW + 1) % m->m_fftSize; - - // if overlap limit reached, process FFTs for each channel - if (!--m->m_fftBufP) - { - for (unsigned int iChan = 0; iChan < m->m_wfx->nChannels; ++iChan) - { - if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT)) - { - // copy from the ring buffer to temp space - memcpy(&m->m_fftTmpIn[0], &(m->m_fftIn[iChan])[m->m_fftBufW], (m->m_fftSize - m->m_fftBufW) * sizeof(float)); - memcpy(&m->m_fftTmpIn[m->m_fftSize - m->m_fftBufW], &m->m_fftIn[iChan][0], m->m_fftBufW * sizeof(float)); - - // apply the windowing function - for (int iBin = 0; iBin < m->m_fftSize; ++iBin) - { - m->m_fftTmpIn[iBin] *= m->m_fftKWdw[iBin]; - } - - kiss_fftr(m->m_fftCfg[iChan], m->m_fftTmpIn, m->m_fftTmpOut); - } - else - { - memset(m->m_fftTmpOut, 0, m->m_fftSize * sizeof(kiss_fft_cpx)); - } - - // filter the bin levels as with peak measurements - for (int iBin = 0; iBin < m->m_fftSize; ++iBin) - { - float x0 = (m->m_fftOut[iChan])[iBin]; - float x1 = (m->m_fftTmpOut[iBin].r * m->m_fftTmpOut[iBin].r + m->m_fftTmpOut[iBin].i * m->m_fftTmpOut[iBin].i) * scalar; - x0 = x1 + m->m_kFFT[(x1 < x0)] * (x0 - x1); - (m->m_fftOut[iChan])[iBin] = x0; - } - } - - m->m_fftBufP = m->m_fftSize - m->m_fftOverlap; - } - } - - // integrate FFT results into log-scale frequency bands - if (m->m_nBands) - { - const float df = (float)m->m_wfx->nSamplesPerSec / m->m_fftSize; - const float scalar = 2.0f / (float)m->m_wfx->nSamplesPerSec; - for (unsigned int iChan = 0; iChan < m->m_wfx->nChannels; ++iChan) - { - memset(m->m_bandOut[iChan], 0, m->m_nBands * sizeof(float)); - int iBin = 0; - int iBand = 0; - float f0 = 0.0f; - - while (iBin <= (m->m_fftSize / 2) && iBand < m->m_nBands) - { - float fLin1 = ((float)iBin + 0.5f) * df; - float fLog1 = m->m_bandFreq[iBand]; - float x = (m->m_fftOut[iChan])[iBin]; - float& y = (m->m_bandOut[iChan])[iBand]; - - if (fLin1 <= fLog1) - { - y += (fLin1 - f0) * x * scalar; - f0 = fLin1; - iBin += 1; - } - else - { - y += (fLog1 - f0) * x * scalar; - f0 = fLog1; - iBand += 1; - } - } - } - } - } - - // release the buffer - m->m_clCapture->ReleaseBuffer(nFrames); - - // mark the time of last buffer update - m->m_pcFill = pcCur; - } - // detect device disconnection - switch (hr) - { - case AUDCLNT_S_BUFFER_EMPTY: - // Windows bug: sometimes when shutting down a playback application, it doesn't zero - // out the buffer. Detect this by checking the time since the last successful fill - // and resetting the volumes if past the threshold. - if (((pcCur.QuadPart - m->m_pcFill.QuadPart) * m->m_pcMult) >= EMPTY_TIMEOUT) - { - for (int iChan = 0; iChan < Measure::MAX_CHANNELS; ++iChan) - { - m->m_rms[iChan] = 0.0; - m->m_peak[iChan] = 0.0; - } - } - break; - - case AUDCLNT_E_BUFFER_ERROR: - case AUDCLNT_E_DEVICE_INVALIDATED: - case AUDCLNT_E_SERVICE_NOT_RUNNING: - m->DeviceRelease(); - break; - } - - m->m_pcPoll = pcCur; - - } - else if (!m->m_parent && !m->m_clCapture && (pcCur.QuadPart - m->m_pcPoll.QuadPart) * m->m_pcMult >= DEVICE_TIMEOUT) - { - // poll for new devices - assert(m->m_enum); - assert(!m->m_dev); - m->DeviceInit(); - m->m_pcPoll = pcCur; - } - - switch (m->m_type) - { - case Measure::TYPE_RMS: - if (m->m_channel == Measure::CHANNEL_SUM) - { - return CLAMP01((sqrt(parent->m_rms[0]) + sqrt(parent->m_rms[1])) * 0.5 * parent->m_gainRMS); - } - else - { - return CLAMP01(sqrt(parent->m_rms[m->m_channel]) * parent->m_gainRMS); - } - break; - - case Measure::TYPE_PEAK: - if (m->m_channel == Measure::CHANNEL_SUM) - { - return CLAMP01((parent->m_peak[0] + parent->m_peak[1]) * 0.5 * parent->m_gainPeak); - } - else - { - return CLAMP01(parent->m_peak[m->m_channel] * parent->m_gainPeak); - } - break; - - case Measure::TYPE_FFT: - if (parent->m_clCapture && parent->m_fftSize) - { - double x; - const int iFFT = m->m_fftIdx; - if (m->m_channel == Measure::CHANNEL_SUM) - { - if (parent->m_wfx->nChannels >= 2) - { - x = (parent->m_fftOut[0][iFFT] + parent->m_fftOut[1][iFFT]) * 0.5; - } - else - { - x = parent->m_fftOut[0][iFFT]; - } - } - else if (m->m_channel < parent->m_wfx->nChannels) - { - x = parent->m_fftOut[m->m_channel][iFFT]; - } - - x = CLAMP01(x); - x = max(0, 10.0 / parent->m_sensitivity * log10(x) + 1.0); - return x; - } - break; - - case Measure::TYPE_BAND: - if (parent->m_clCapture && parent->m_nBands) - { - double x; - const int iBand = m->m_bandIdx; - if (m->m_channel == Measure::CHANNEL_SUM) - { - if (parent->m_wfx->nChannels >= 2) - { - x = (parent->m_bandOut[0][iBand] + parent->m_bandOut[1][iBand]) * 0.5; - } - else - { - x = parent->m_bandOut[0][iBand]; - } - } - else if (m->m_channel < parent->m_wfx->nChannels) - { - x = parent->m_bandOut[m->m_channel][iBand]; - } - - x = CLAMP01(x); - x = max(0, 10.0 / parent->m_sensitivity * log10(x) + 1.0); - return x; - } - break; - - case Measure::TYPE_FFTFREQ: - if (parent->m_clCapture && parent->m_fftSize && m->m_fftIdx <= (parent->m_fftSize / 2)) - { - return (m->m_fftIdx * m->m_wfx->nSamplesPerSec / parent->m_fftSize); - } - break; - - case Measure::TYPE_BANDFREQ: - if (parent->m_clCapture && parent->m_nBands && m->m_bandIdx < parent->m_nBands) - { - return parent->m_bandFreq[m->m_bandIdx]; - } - break; - - case Measure::TYPE_DEV_STATUS: - if (parent->m_dev) - { - DWORD state; - if (parent->m_dev->GetState(&state) == S_OK && state == DEVICE_STATE_ACTIVE) - { - return 1.0; - } - } - break; - } - - return 0.0; -} - - -/** - * Get a string value from the measure. - * - * @param[in] data Measure instance pointer. - * @return String value - must be copied out by the caller. - */ -PLUGIN_EXPORT LPCWSTR GetString (void* data) -{ - Measure* m = (Measure*)data; - Measure* parent = m->m_parent ? m->m_parent : m; - - const WCHAR* s_fmtName[Measure::NUM_FORMATS] = - { - L"", // FMT_INVALID - L"PCM 16b", // FMT_PCM_S16 - L"PCM 32b", // FMT_PCM_F32 - }; - - static WCHAR buffer[4096]; - buffer[0] = '\0'; - - switch (m->m_type) - { - default: - // return NULL for any numeric values, so Rainmeter can auto-convert them. - return NULL; - - case Measure::TYPE_FORMAT: - if (parent->m_wfx) - { - _snwprintf_s(buffer, _TRUNCATE, L"%dHz %s %dch", parent->m_wfx->nSamplesPerSec, - s_fmtName[parent->m_format], parent->m_wfx->nChannels); - } - break; - - case Measure::TYPE_DEV_NAME: - return parent->m_devName; - - case Measure::TYPE_DEV_ID: - if (parent->m_dev) - { - LPWSTR pwszID = NULL; - if (parent->m_dev->GetId(&pwszID) == S_OK) - { - _snwprintf_s(buffer, _TRUNCATE, L"%s", pwszID); - CoTaskMemFree(pwszID); - } - } - break; - - case Measure::TYPE_DEV_LIST: - if (parent->m_enum) - { - IMMDeviceCollection* collection = NULL; - if (parent->m_enum->EnumAudioEndpoints(parent->m_port == Measure::PORT_OUTPUT ? eRender : eCapture, - DEVICE_STATE_ACTIVE | DEVICE_STATE_UNPLUGGED, &collection) == S_OK) - { - UINT nDevices; - collection->GetCount(&nDevices); - - std::wstring strDevices; - - for (ULONG iDevice = 0; iDevice < nDevices; ++iDevice) - { - IMMDevice* device = NULL; - IPropertyStore* props = NULL; - if (collection->Item(iDevice, &device) == S_OK && device->OpenPropertyStore(STGM_READ, &props) == S_OK) - { - LPWSTR id = NULL; - PROPVARIANT varName; - PropVariantInit(&varName); - - if (device->GetId(&id) == S_OK && props->GetValue(PKEY_Device_FriendlyName, &varName) == S_OK) - { - strDevices += (iDevice > 0) ? L"\n" : L""; - strDevices += id; - strDevices += L": "; - strDevices += varName.pwszVal; - } - - if (id) CoTaskMemFree(id); - - PropVariantClear(&varName); - } - - SAFE_RELEASE(props); - SAFE_RELEASE(device); - } - - _snwprintf_s(buffer, _TRUNCATE, L"%s", strDevices.c_str()); - } - - SAFE_RELEASE(collection); - } - break; - } - - return buffer; -} - - -/** - * Try to initialize the default device for the specified port. - * - * @return Result value, S_OK on success. - */ -HRESULT Measure::DeviceInit () -{ - HRESULT hr; - - // get the device handle - assert(m_enum && !m_dev); - - // if a specific ID was requested, search for that one, otherwise get the default - if (*m_reqID) - { - hr = m_enum->GetDevice(m_reqID, &m_dev); - if (hr != S_OK) - { - WCHAR msg[256]; - _snwprintf_s(msg, _TRUNCATE, L"Audio %s device '%s' not found (error 0x%08x).", - m_port==PORT_OUTPUT ? L"output" : L"input", m_reqID, hr); - - RmLog(LOG_WARNING, msg); - } - } - else - { - hr = m_enum->GetDefaultAudioEndpoint(m_port==PORT_OUTPUT ? eRender : eCapture, eConsole, &m_dev); - } - - EXIT_ON_ERROR(hr); - - // store device name - IPropertyStore* props = NULL; - if (m_dev->OpenPropertyStore(STGM_READ, &props) == S_OK) - { - PROPVARIANT varName; - PropVariantInit(&varName); - - if (props->GetValue(PKEY_Device_FriendlyName, &varName) == S_OK) - { - _snwprintf_s(m_devName, _TRUNCATE, L"%s", varName.pwszVal); - } - - PropVariantClear(&varName); - } - - SAFE_RELEASE(props); - -#if (WINDOWS_BUG_WORKAROUND) - // get an extra audio client for the dummy silent channel - hr = m_dev->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&m_clBugAudio); - if (hr != S_OK) - { - RmLog(LOG_WARNING, L"Failed to create audio client for Windows bug workaround."); - } -#endif - - // get the main audio client - hr = m_dev->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&m_clAudio); - if (hr != S_OK) - { - RmLog(LOG_WARNING, L"Failed to create audio client."); - } - - EXIT_ON_ERROR(hr); - - // parse audio format - Note: not all formats are supported. - hr = m_clAudio->GetMixFormat(&m_wfx); - EXIT_ON_ERROR(hr); - - switch (m_wfx->wFormatTag) - { - case WAVE_FORMAT_PCM: - if (m_wfx->wBitsPerSample == 16) - { - m_format = FMT_PCM_S16; - } - break; - - case WAVE_FORMAT_IEEE_FLOAT: - m_format = FMT_PCM_F32; - break; - - case WAVE_FORMAT_EXTENSIBLE: - if (reinterpret_cast(m_wfx)->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) - { - m_format = FMT_PCM_F32; - } - break; - } - - if (m_format == FMT_INVALID) - { - RmLog(LOG_WARNING, L"Invalid sample format. Only PCM 16b integer or PCM 32b float are supported."); - } - - // setup FFT buffers - if (m_fftSize) - { - for (int iChan = 0; iChan < m_wfx->nChannels; ++iChan) - { - m_fftCfg[iChan] = kiss_fftr_alloc(m_fftSize, 0, NULL, NULL); - m_fftIn[iChan] = (float*)calloc(m_fftSize * sizeof(float), 1); - m_fftOut[iChan] = (float*)calloc(m_fftSize * sizeof(float), 1); - } - - m_fftKWdw = (float*)calloc(m_fftSize * sizeof(float), 1); - m_fftTmpIn = (float*)calloc(m_fftSize * sizeof(float), 1); - m_fftTmpOut = (kiss_fft_cpx*)calloc(m_fftSize * sizeof(kiss_fft_cpx), 1); - m_fftBufP = m_fftSize - m_fftOverlap; - - // calculate window function coefficients (http://en.wikipedia.org/wiki/Window_function#Hann_.28Hanning.29_window) - for (int iBin = 0; iBin < m_fftSize; ++iBin) - { - m_fftKWdw[iBin] = (float)(0.5 * (1.0 - cos(TWOPI * iBin / (m_fftSize - 1)))); - } - } - - // calculate band frequencies and allocate band output buffers - if (m_nBands) - { - m_bandFreq = (float*)malloc(m_nBands * sizeof(float)); - const double step = (log(m_freqMax / m_freqMin) / m_nBands) / log(2.0); - m_bandFreq[0] = (float)(m_freqMin * pow(2.0, step / 2.0)); - - for (int iBand = 1; iBand < m_nBands; ++iBand) - { - m_bandFreq[iBand] = (float)(m_bandFreq[iBand - 1] * pow(2.0, step)); - } - - for (int iChan = 0; iChan < m_wfx->nChannels; ++iChan) - { - m_bandOut[iChan] = (float*)calloc(m_nBands * sizeof(float), 1); - } - } - - REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC; - -#if (WINDOWS_BUG_WORKAROUND) - // --------------------------------------------------------------------------------------- - // Windows bug workaround: create a silent render client before initializing loopback mode - // see: http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/c7ba0a04-46ce-43ff-ad15-ce8932c00171/loopback-recording-causes-digital-stuttering?forum=windowspro-audiodevelopment - if (m_port == PORT_OUTPUT) - { - hr = m_clBugAudio->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, hnsRequestedDuration, 0, m_wfx, NULL); - EXIT_ON_ERROR(hr); - - // get the frame count - UINT32 nFrames; - hr = m_clBugAudio->GetBufferSize(&nFrames); - EXIT_ON_ERROR(hr); - - // create a render client - hr = m_clBugAudio->GetService(IID_IAudioRenderClient, (void**)&m_clBugRender); - EXIT_ON_ERROR(hr); - - // get the buffer - BYTE* buffer; - hr = m_clBugRender->GetBuffer(nFrames, &buffer); - EXIT_ON_ERROR(hr); - - // release it - hr = m_clBugRender->ReleaseBuffer(nFrames, AUDCLNT_BUFFERFLAGS_SILENT); - EXIT_ON_ERROR(hr); - - // start the stream - hr = m_clBugAudio->Start(); - EXIT_ON_ERROR(hr); - } - // --------------------------------------------------------------------------------------- -#endif - - // initialize the audio client - hr = m_clAudio->Initialize(AUDCLNT_SHAREMODE_SHARED, m_port == PORT_OUTPUT ? AUDCLNT_STREAMFLAGS_LOOPBACK : 0, - hnsRequestedDuration, 0, m_wfx, NULL); - if (hr != S_OK) - { - // Compatibility with the Nahimic audio driver - // https://github.com/rainmeter/rainmeter/commit/0a3dfa35357270512ec4a3c722674b67bff541d6 - // https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/bd8cd9f2-974f-4a9f-8e9c-e83001819942/iaudioclient-initialize-failure - - // initialization failed, try to use stereo waveformat - m_wfx->nChannels = 2; - m_wfx->nBlockAlign = (2 * m_wfx->wBitsPerSample) / 8; - m_wfx->nAvgBytesPerSec = m_wfx->nSamplesPerSec * m_wfx->nBlockAlign; - - hr = m_clAudio->Initialize(AUDCLNT_SHAREMODE_SHARED, m_port == PORT_OUTPUT ? AUDCLNT_STREAMFLAGS_LOOPBACK : 0, - hnsRequestedDuration, 0, m_wfx, NULL); - if (hr != S_OK) - { - // stereo waveformat didnt work either, throw an error - RmLog(LOG_WARNING, L"Failed to initialize audio client."); - } - } - EXIT_ON_ERROR(hr); - - // initialize the audio capture client - hr = m_clAudio->GetService(IID_IAudioCaptureClient, (void**)&m_clCapture); - if (hr != S_OK) - { - RmLog(LOG_WARNING, L"Failed to create audio capture client."); - } - EXIT_ON_ERROR(hr); - - // start the stream - hr = m_clAudio->Start(); - if (hr != S_OK) - { - RmLog(LOG_WARNING, L"Failed to start the stream."); - } - EXIT_ON_ERROR(hr); - - // initialize the watchdog timer - QueryPerformanceCounter(&m_pcFill); - - return S_OK; - -Exit: - DeviceRelease(); - return hr; -} - - -/** - * Release handles to audio resources. (except the enumerator) - */ -void Measure::DeviceRelease () -{ -#if (WINDOWS_BUG_WORKAROUND) - if (m_clBugAudio) - { - if (!m_parent) RmLog(LOG_DEBUG, L"Releasing dummy stream audio device."); - m_clBugAudio->Stop(); - } - SAFE_RELEASE(m_clBugRender); - SAFE_RELEASE(m_clBugAudio); -#endif - - if (m_clAudio) - { - if (!m_parent) RmLog(LOG_DEBUG, L"Releasing audio device."); - m_clAudio->Stop(); - } - - SAFE_RELEASE(m_clCapture); - - if (m_wfx) - { - CoTaskMemFree(m_wfx); - m_wfx = NULL; - } - - SAFE_RELEASE(m_clAudio); - SAFE_RELEASE(m_dev); - - for (int iChan = 0; iChan < Measure::MAX_CHANNELS; ++iChan) - { - if (m_fftCfg[iChan]) kiss_fftr_free(m_fftCfg[iChan]); - m_fftCfg[iChan] = NULL; - - if (m_fftIn[iChan]) free(m_fftIn[iChan]); - m_fftIn[iChan] = NULL; - - if (m_fftOut[iChan]) free(m_fftOut[iChan]); - m_fftOut[iChan] = NULL; - - if (m_bandOut[iChan]) free(m_bandOut[iChan]); - m_bandOut[iChan] = NULL; - - m_rms[iChan] = 0.0; - m_peak[iChan] = 0.0; - } - - if (m_bandFreq) - { - free(m_bandFreq); - m_bandFreq = NULL; - } - - if (m_fftTmpOut) - { - free(m_fftTmpOut); - free(m_fftTmpIn); - free(m_fftKWdw); - m_fftTmpOut = NULL; - m_fftTmpIn = NULL; - m_fftKWdw = NULL; - kiss_fft_cleanup(); - } - - m_devName[0] = '\0'; - m_format = FMT_INVALID; -} + RmLogF(rm, LOG_ERROR, L"Invalid Type: '%s', must be one of: %s", type, typeNames.c_str()); + } + } + + // parse FFT index request + m->m_fftIdx = max(0, RmReadInt(rm, L"FFTIdx", m->m_fftIdx)); + m->m_fftIdx = m->m_parent ? + min(m->m_parent->m_fftSize / 2, m->m_fftIdx) : + min(m->m_fftSize / 2, m->m_fftIdx); + + // parse band index request + m->m_bandIdx = max(0, RmReadInt(rm, L"BandIdx", m->m_bandIdx)); + m->m_bandIdx = m->m_parent ? + min(m->m_parent->m_nBands, m->m_bandIdx) : + min(m->m_nBands, m->m_bandIdx); + + // parse envelope values on parents only + if (!m->m_parent) + { + // (re)parse envelope values + m->m_envRMS[0] = max(0, RmReadInt(rm, L"RMSAttack", m->m_envRMS[0])); + m->m_envRMS[1] = max(0, RmReadInt(rm, L"RMSDecay", m->m_envRMS[1])); + m->m_envPeak[0] = max(0, RmReadInt(rm, L"PeakAttack", m->m_envPeak[0])); + m->m_envPeak[1] = max(0, RmReadInt(rm, L"PeakDecay", m->m_envPeak[1])); + m->m_envFFT[0] = max(0, RmReadInt(rm, L"FFTAttack", m->m_envFFT[0])); + m->m_envFFT[1] = max(0, RmReadInt(rm, L"FFTDecay", m->m_envFFT[1])); + + // (re)parse gain constants + m->m_gainRMS = max(0.0, RmReadDouble(rm, L"RMSGain", m->m_gainRMS)); + m->m_gainPeak = max(0.0, RmReadDouble(rm, L"PeakGain", m->m_gainPeak)); + m->m_sensitivity = max(1.0, RmReadDouble(rm, L"Sensitivity", m->m_sensitivity)); + + // regenerate filter constants + if (m->m_wfx) + { + const double freq = m->m_wfx->nSamplesPerSec; + m->m_kRMS[0] = (float) exp(log10(0.01) / (freq * (double)m->m_envRMS[0] * 0.001)); + m->m_kRMS[1] = (float) exp(log10(0.01) / (freq * (double)m->m_envRMS[1] * 0.001)); + m->m_kPeak[0] = (float) exp(log10(0.01) / (freq * (double)m->m_envPeak[0] * 0.001)); + m->m_kPeak[1] = (float) exp(log10(0.01) / (freq * (double)m->m_envPeak[1] * 0.001)); + + if (m->m_fftSize) + { + m->m_kFFT[0] = (float) exp(log10(0.01) / (freq / (m->m_fftSize-m->m_fftOverlap) * (double)m->m_envFFT[0] * 0.001)); + m->m_kFFT[1] = (float) exp(log10(0.01) / (freq / (m->m_fftSize-m->m_fftOverlap) * (double)m->m_envFFT[1] * 0.001)); + } + } + } +} + + +/** + * Update the measure. + * + * @param[in] data Measure instance pointer. + * @return Latest value - typically an audio level between 0.0 and 1.0. + */ +PLUGIN_EXPORT double Update (void* data) +{ + Measure* m = (Measure*)data; + Measure* parent = m->m_parent ? m->m_parent : m; + LARGE_INTEGER pcCur; + QueryPerformanceCounter(&pcCur); + + // query the buffer + if (m->m_clCapture && (pcCur.QuadPart - m->m_pcPoll.QuadPart) * m->m_pcMult >= QUERY_TIMEOUT) + { + BYTE* buffer; + UINT32 nFrames; + DWORD flags; + UINT64 pos; + HRESULT hr; + + while ((hr = m->m_clCapture->GetBuffer(&buffer, &nFrames, &flags, &pos, NULL)) == S_OK) + { + // measure RMS and peak levels + float rms[Measure::MAX_CHANNELS]; + float peak[Measure::MAX_CHANNELS]; + for (int iChan = 0; iChan < Measure::MAX_CHANNELS; ++iChan) + { + rms[iChan] = (float)m->m_rms[iChan]; + peak[iChan] = (float)m->m_peak[iChan]; + } + + // loops unrolled for float, 16b and mono, stereo + if (m->m_format == Measure::FMT_PCM_F32) + { + float* s = (float*)buffer; + if (m->m_wfx->nChannels == 1) + { + for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) + { + float xL = (float)*s++; + float sqrL = xL * xL; + float absL = abs(xL); + rms[0] = sqrL + m->m_kRMS[(sqrL < rms[0])] * (rms[0] - sqrL); + peak[0] = absL + m->m_kPeak[(absL < peak[0])] * (peak[0] - absL); + rms[1] = rms[0]; + peak[1] = peak[0]; + } + } + else if (m->m_wfx->nChannels == 2) + { + for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) + { + float xL = (float)*s++; + float xR = (float)*s++; + float sqrL = xL * xL; + float sqrR = xR * xR; + float absL = abs(xL); + float absR = abs(xR); + rms[0] = sqrL + m->m_kRMS[(sqrL < rms[0])] * (rms[0] - sqrL); + rms[1] = sqrR + m->m_kRMS[(sqrR < rms[1])] * (rms[1] - sqrR); + peak[0] = absL + m->m_kPeak[(absL < peak[0])] * (peak[0] - absL); + peak[1] = absR + m->m_kPeak[(absR < peak[1])] * (peak[1] - absR); + } + } + else + { + for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) + { + for (unsigned int iChan = 0; iChan < m->m_wfx->nChannels; ++iChan) + { + float x = (float)*s++; + float sqrX = x * x; + float absX = abs(x); + rms[iChan] = sqrX + m->m_kRMS[(sqrX < rms[iChan])] * (rms[iChan] - sqrX); + peak[iChan] = absX + m->m_kPeak[(absX < peak[iChan])] * (peak[iChan] - absX); + } + } + } + } + else if (m->m_format == Measure::FMT_PCM_S16) + { + INT16* s = (INT16*)buffer; + if (m->m_wfx->nChannels == 1) + { + for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) + { + float xL = (float)*s++ * 1.0f / 0x7fff; + float sqrL = xL * xL; + float absL = abs(xL); + rms[0] = sqrL + m->m_kRMS[(sqrL < rms[0])] * (rms[0] - sqrL); + peak[0] = absL + m->m_kPeak[(absL < peak[0])] * (peak[0] - absL); + rms[1] = rms[0]; + peak[1] = peak[0]; + } + } + else if (m->m_wfx->nChannels == 2) + { + for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) + { + float xL = (float)*s++ * 1.0f / 0x7fff; + float xR = (float)*s++ * 1.0f / 0x7fff; + float sqrL = xL * xL; + float sqrR = xR * xR; + float absL = abs(xL); + float absR = abs(xR); + rms[0] = sqrL + m->m_kRMS[(sqrL < rms[0])] * (rms[0] - sqrL); + rms[1] = sqrR + m->m_kRMS[(sqrR < rms[1])] * (rms[1] - sqrR); + peak[0] = absL + m->m_kPeak[(absL < peak[0])] * (peak[0] - absL); + peak[1] = absR + m->m_kPeak[(absR < peak[1])] * (peak[1] - absR); + } + } + else + { + for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) + { + for (unsigned int iChan = 0; iChan < m->m_wfx->nChannels; ++iChan) + { + float x = (float)*s++ * 1.0f / 0x7fff; + float sqrX = x * x; + float absX = abs(x); + rms[iChan] = sqrX + m->m_kRMS[(sqrX < rms[iChan])] * (rms[iChan] - sqrX); + peak[iChan] = absX + m->m_kPeak[(absX < peak[iChan])] * (peak[iChan] - absX); + } + } + } + } + + for (int iChan = 0; iChan < Measure::MAX_CHANNELS; ++iChan) + { + m->m_rms[iChan] = rms[iChan]; + m->m_peak[iChan] = peak[iChan]; + } + + // process FFTs (optional) + if (m->m_fftSize) + { + float* sF32 = (float*)buffer; + INT16* sI16 = (INT16*)buffer; + const float scalar = (float)(1.0 / sqrt(m->m_fftSize)); + + for (unsigned int iFrame = 0; iFrame < nFrames; ++iFrame) + { + // fill ring buffers (demux streams) + for (unsigned int iChan = 0; iChan < m->m_wfx->nChannels; ++iChan) + { + (m->m_fftIn[iChan])[m->m_fftBufW] = m->m_format == Measure::FMT_PCM_F32 ? *sF32++ : (float)*sI16++ * 1.0f / 0x7fff; + } + + m->m_fftBufW = (m->m_fftBufW + 1) % m->m_fftSize; + + // if overlap limit reached, process FFTs for each channel + if (!--m->m_fftBufP) + { + for (unsigned int iChan = 0; iChan < m->m_wfx->nChannels; ++iChan) + { + if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT)) + { + // copy from the ring buffer to temp space + memcpy(&m->m_fftTmpIn[0], &(m->m_fftIn[iChan])[m->m_fftBufW], (m->m_fftSize - m->m_fftBufW) * sizeof(float)); + memcpy(&m->m_fftTmpIn[m->m_fftSize - m->m_fftBufW], &m->m_fftIn[iChan][0], m->m_fftBufW * sizeof(float)); + + // apply the windowing function + for (int iBin = 0; iBin < m->m_fftSize; ++iBin) + { + m->m_fftTmpIn[iBin] *= m->m_fftKWdw[iBin]; + } + + kiss_fftr(m->m_fftCfg[iChan], m->m_fftTmpIn, m->m_fftTmpOut); + } + else + { + memset(m->m_fftTmpOut, 0, m->m_fftSize * sizeof(kiss_fft_cpx)); + } + + // filter the bin levels as with peak measurements + for (int iBin = 0; iBin < m->m_fftSize; ++iBin) + { + float x0 = (m->m_fftOut[iChan])[iBin]; + float x1 = (m->m_fftTmpOut[iBin].r * m->m_fftTmpOut[iBin].r + m->m_fftTmpOut[iBin].i * m->m_fftTmpOut[iBin].i) * scalar; + x0 = x1 + m->m_kFFT[(x1 < x0)] * (x0 - x1); + (m->m_fftOut[iChan])[iBin] = x0; + } + } + + m->m_fftBufP = m->m_fftSize - m->m_fftOverlap; + } + } + + // integrate FFT results into log-scale frequency bands + if (m->m_nBands) + { + const float df = (float)m->m_wfx->nSamplesPerSec / m->m_fftSize; + const float scalar = 2.0f / (float)m->m_wfx->nSamplesPerSec; + for (unsigned int iChan = 0; iChan < m->m_wfx->nChannels; ++iChan) + { + memset(m->m_bandOut[iChan], 0, m->m_nBands * sizeof(float)); + int iBin = 0; + int iBand = 0; + float f0 = 0.0f; + + while (iBin <= (m->m_fftSize / 2) && iBand < m->m_nBands) + { + float fLin1 = ((float)iBin + 0.5f) * df; + float fLog1 = m->m_bandFreq[iBand]; + float x = (m->m_fftOut[iChan])[iBin]; + float& y = (m->m_bandOut[iChan])[iBand]; + + if (fLin1 <= fLog1) + { + y += (fLin1 - f0) * x * scalar; + f0 = fLin1; + iBin += 1; + } + else + { + y += (fLog1 - f0) * x * scalar; + f0 = fLog1; + iBand += 1; + } + } + } + } + } + + // release the buffer + m->m_clCapture->ReleaseBuffer(nFrames); + + // mark the time of last buffer update + m->m_pcFill = pcCur; + } + // detect device disconnection + switch (hr) + { + case AUDCLNT_S_BUFFER_EMPTY: + // Windows bug: sometimes when shutting down a playback application, it doesn't zero + // out the buffer. Detect this by checking the time since the last successful fill + // and resetting the volumes if past the threshold. + if (((pcCur.QuadPart - m->m_pcFill.QuadPart) * m->m_pcMult) >= EMPTY_TIMEOUT) + { + for (int iChan = 0; iChan < Measure::MAX_CHANNELS; ++iChan) + { + m->m_rms[iChan] = 0.0; + m->m_peak[iChan] = 0.0; + } + } + break; + + case AUDCLNT_E_BUFFER_ERROR: + case AUDCLNT_E_DEVICE_INVALIDATED: + case AUDCLNT_E_SERVICE_NOT_RUNNING: + m->DeviceRelease(); + break; + } + + m->m_pcPoll = pcCur; + + } + else if (!m->m_parent && !m->m_clCapture && (pcCur.QuadPart - m->m_pcPoll.QuadPart) * m->m_pcMult >= DEVICE_TIMEOUT) + { + // poll for new devices + assert(m->m_enum); + assert(!m->m_dev); + m->DeviceInit(); + m->m_pcPoll = pcCur; + } + + switch (m->m_type) + { + case Measure::TYPE_RMS: + if (m->m_channel == Measure::CHANNEL_SUM) + { + return CLAMP01((sqrt(parent->m_rms[0]) + sqrt(parent->m_rms[1])) * 0.5 * parent->m_gainRMS); + } + else + { + return CLAMP01(sqrt(parent->m_rms[m->m_channel]) * parent->m_gainRMS); + } + break; + + case Measure::TYPE_PEAK: + if (m->m_channel == Measure::CHANNEL_SUM) + { + return CLAMP01((parent->m_peak[0] + parent->m_peak[1]) * 0.5 * parent->m_gainPeak); + } + else + { + return CLAMP01(parent->m_peak[m->m_channel] * parent->m_gainPeak); + } + break; + + case Measure::TYPE_FFT: + if (parent->m_clCapture && parent->m_fftSize) + { + double x; + const int iFFT = m->m_fftIdx; + if (m->m_channel == Measure::CHANNEL_SUM) + { + if (parent->m_wfx->nChannels >= 2) + { + x = (parent->m_fftOut[0][iFFT] + parent->m_fftOut[1][iFFT]) * 0.5; + } + else + { + x = parent->m_fftOut[0][iFFT]; + } + } + else if (m->m_channel < parent->m_wfx->nChannels) + { + x = parent->m_fftOut[m->m_channel][iFFT]; + } + + x = CLAMP01(x); + x = max(0, 10.0 / parent->m_sensitivity * log10(x) + 1.0); + return x; + } + break; + + case Measure::TYPE_BAND: + if (parent->m_clCapture && parent->m_nBands) + { + double x; + const int iBand = m->m_bandIdx; + if (m->m_channel == Measure::CHANNEL_SUM) + { + if (parent->m_wfx->nChannels >= 2) + { + x = (parent->m_bandOut[0][iBand] + parent->m_bandOut[1][iBand]) * 0.5; + } + else + { + x = parent->m_bandOut[0][iBand]; + } + } + else if (m->m_channel < parent->m_wfx->nChannels) + { + x = parent->m_bandOut[m->m_channel][iBand]; + } + + x = CLAMP01(x); + x = max(0, 10.0 / parent->m_sensitivity * log10(x) + 1.0); + return x; + } + break; + + case Measure::TYPE_FFTFREQ: + if (parent->m_clCapture && parent->m_fftSize && m->m_fftIdx <= (parent->m_fftSize / 2)) + { + return (m->m_fftIdx * m->m_wfx->nSamplesPerSec / parent->m_fftSize); + } + break; + + case Measure::TYPE_BANDFREQ: + if (parent->m_clCapture && parent->m_nBands && m->m_bandIdx < parent->m_nBands) + { + return parent->m_bandFreq[m->m_bandIdx]; + } + break; + + case Measure::TYPE_DEV_STATUS: + if (parent->m_dev) + { + DWORD state; + if (parent->m_dev->GetState(&state) == S_OK && state == DEVICE_STATE_ACTIVE) + { + return 1.0; + } + } + break; + } + + return 0.0; +} + + +/** + * Get a string value from the measure. + * + * @param[in] data Measure instance pointer. + * @return String value - must be copied out by the caller. + */ +PLUGIN_EXPORT LPCWSTR GetString (void* data) +{ + Measure* m = (Measure*)data; + Measure* parent = m->m_parent ? m->m_parent : m; + + const WCHAR* s_fmtName[Measure::NUM_FORMATS] = + { + L"", // FMT_INVALID + L"PCM 16b", // FMT_PCM_S16 + L"PCM 32b", // FMT_PCM_F32 + }; + + static WCHAR buffer[4096]; + buffer[0] = '\0'; + + switch (m->m_type) + { + default: + // return NULL for any numeric values, so Rainmeter can auto-convert them. + return NULL; + + case Measure::TYPE_FORMAT: + if (parent->m_wfx) + { + _snwprintf_s(buffer, _TRUNCATE, L"%dHz %s %dch", parent->m_wfx->nSamplesPerSec, + s_fmtName[parent->m_format], parent->m_wfx->nChannels); + } + break; + + case Measure::TYPE_DEV_NAME: + return parent->m_devName; + + case Measure::TYPE_DEV_ID: + if (parent->m_dev) + { + LPWSTR pwszID = NULL; + if (parent->m_dev->GetId(&pwszID) == S_OK) + { + _snwprintf_s(buffer, _TRUNCATE, L"%s", pwszID); + CoTaskMemFree(pwszID); + } + } + break; + + case Measure::TYPE_DEV_LIST: + if (parent->m_enum) + { + IMMDeviceCollection* collection = NULL; + if (parent->m_enum->EnumAudioEndpoints(parent->m_port == Measure::PORT_OUTPUT ? eRender : eCapture, + DEVICE_STATE_ACTIVE | DEVICE_STATE_UNPLUGGED, &collection) == S_OK) + { + UINT nDevices; + collection->GetCount(&nDevices); + + std::wstring strDevices; + + for (ULONG iDevice = 0; iDevice < nDevices; ++iDevice) + { + IMMDevice* device = NULL; + IPropertyStore* props = NULL; + if (collection->Item(iDevice, &device) == S_OK && device->OpenPropertyStore(STGM_READ, &props) == S_OK) + { + LPWSTR id = NULL; + PROPVARIANT varName; + PropVariantInit(&varName); + + if (device->GetId(&id) == S_OK && props->GetValue(PKEY_Device_FriendlyName, &varName) == S_OK) + { + strDevices += (iDevice > 0) ? L"\n" : L""; + strDevices += id; + strDevices += L": "; + strDevices += varName.pwszVal; + } + + if (id) CoTaskMemFree(id); + + PropVariantClear(&varName); + } + + SAFE_RELEASE(props); + SAFE_RELEASE(device); + } + + _snwprintf_s(buffer, _TRUNCATE, L"%s", strDevices.c_str()); + } + + SAFE_RELEASE(collection); + } + break; + } + + return buffer; +} + + +/** + * Try to initialize the default device for the specified port. + * + * @return Result value, S_OK on success. + */ +HRESULT Measure::DeviceInit () +{ + HRESULT hr; + + // get the device handle + assert(m_enum && !m_dev); + + // if a specific ID was requested, search for that one, otherwise get the default + if (*m_reqID) + { + hr = m_enum->GetDevice(m_reqID, &m_dev); + if (hr != S_OK) + { + WCHAR msg[256]; + _snwprintf_s(msg, _TRUNCATE, L"Audio %s device '%s' not found (error 0x%08x).", + m_port==PORT_OUTPUT ? L"output" : L"input", m_reqID, hr); + + RmLog(LOG_WARNING, msg); + } + } + else + { + hr = m_enum->GetDefaultAudioEndpoint(m_port==PORT_OUTPUT ? eRender : eCapture, eConsole, &m_dev); + } + + EXIT_ON_ERROR(hr); + + // store device name + IPropertyStore* props = NULL; + if (m_dev->OpenPropertyStore(STGM_READ, &props) == S_OK) + { + PROPVARIANT varName; + PropVariantInit(&varName); + + if (props->GetValue(PKEY_Device_FriendlyName, &varName) == S_OK) + { + _snwprintf_s(m_devName, _TRUNCATE, L"%s", varName.pwszVal); + } + + PropVariantClear(&varName); + } + + SAFE_RELEASE(props); + +#if (WINDOWS_BUG_WORKAROUND) + // get an extra audio client for the dummy silent channel + hr = m_dev->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&m_clBugAudio); + if (hr != S_OK) + { + RmLog(LOG_WARNING, L"Failed to create audio client for Windows bug workaround."); + } +#endif + + // get the main audio client + hr = m_dev->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&m_clAudio); + if (hr != S_OK) + { + RmLog(LOG_WARNING, L"Failed to create audio client."); + } + + EXIT_ON_ERROR(hr); + + // parse audio format - Note: not all formats are supported. + hr = m_clAudio->GetMixFormat(&m_wfx); + EXIT_ON_ERROR(hr); + + switch (m_wfx->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (m_wfx->wBitsPerSample == 16) + { + m_format = FMT_PCM_S16; + } + break; + + case WAVE_FORMAT_IEEE_FLOAT: + m_format = FMT_PCM_F32; + break; + + case WAVE_FORMAT_EXTENSIBLE: + if (reinterpret_cast(m_wfx)->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) + { + m_format = FMT_PCM_F32; + } + break; + } + + if (m_format == FMT_INVALID) + { + RmLog(LOG_WARNING, L"Invalid sample format. Only PCM 16b integer or PCM 32b float are supported."); + } + + // setup FFT buffers + if (m_fftSize) + { + for (int iChan = 0; iChan < m_wfx->nChannels; ++iChan) + { + m_fftCfg[iChan] = kiss_fftr_alloc(m_fftSize, 0, NULL, NULL); + m_fftIn[iChan] = (float*)calloc(m_fftSize * sizeof(float), 1); + m_fftOut[iChan] = (float*)calloc(m_fftSize * sizeof(float), 1); + } + + m_fftKWdw = (float*)calloc(m_fftSize * sizeof(float), 1); + m_fftTmpIn = (float*)calloc(m_fftSize * sizeof(float), 1); + m_fftTmpOut = (kiss_fft_cpx*)calloc(m_fftSize * sizeof(kiss_fft_cpx), 1); + m_fftBufP = m_fftSize - m_fftOverlap; + + // calculate window function coefficients (http://en.wikipedia.org/wiki/Window_function#Hann_.28Hanning.29_window) + for (int iBin = 0; iBin < m_fftSize; ++iBin) + { + m_fftKWdw[iBin] = (float)(0.5 * (1.0 - cos(TWOPI * iBin / (m_fftSize - 1)))); + } + } + + // calculate band frequencies and allocate band output buffers + if (m_nBands) + { + m_bandFreq = (float*)malloc(m_nBands * sizeof(float)); + const double step = (log(m_freqMax / m_freqMin) / m_nBands) / log(2.0); + m_bandFreq[0] = (float)(m_freqMin * pow(2.0, step / 2.0)); + + for (int iBand = 1; iBand < m_nBands; ++iBand) + { + m_bandFreq[iBand] = (float)(m_bandFreq[iBand - 1] * pow(2.0, step)); + } + + for (int iChan = 0; iChan < m_wfx->nChannels; ++iChan) + { + m_bandOut[iChan] = (float*)calloc(m_nBands * sizeof(float), 1); + } + } + + REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC; + +#if (WINDOWS_BUG_WORKAROUND) + // --------------------------------------------------------------------------------------- + // Windows bug workaround: create a silent render client before initializing loopback mode + // see: http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/c7ba0a04-46ce-43ff-ad15-ce8932c00171/loopback-recording-causes-digital-stuttering?forum=windowspro-audiodevelopment + if (m_port == PORT_OUTPUT) + { + hr = m_clBugAudio->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, hnsRequestedDuration, 0, m_wfx, NULL); + EXIT_ON_ERROR(hr); + + // get the frame count + UINT32 nFrames; + hr = m_clBugAudio->GetBufferSize(&nFrames); + EXIT_ON_ERROR(hr); + + // create a render client + hr = m_clBugAudio->GetService(IID_IAudioRenderClient, (void**)&m_clBugRender); + EXIT_ON_ERROR(hr); + + // get the buffer + BYTE* buffer; + hr = m_clBugRender->GetBuffer(nFrames, &buffer); + EXIT_ON_ERROR(hr); + + // release it + hr = m_clBugRender->ReleaseBuffer(nFrames, AUDCLNT_BUFFERFLAGS_SILENT); + EXIT_ON_ERROR(hr); + + // start the stream + hr = m_clBugAudio->Start(); + EXIT_ON_ERROR(hr); + } + // --------------------------------------------------------------------------------------- +#endif + + // initialize the audio client + hr = m_clAudio->Initialize(AUDCLNT_SHAREMODE_SHARED, m_port == PORT_OUTPUT ? AUDCLNT_STREAMFLAGS_LOOPBACK : 0, + hnsRequestedDuration, 0, m_wfx, NULL); + if (hr != S_OK) + { + // Compatibility with the Nahimic audio driver + // https://github.com/rainmeter/rainmeter/commit/0a3dfa35357270512ec4a3c722674b67bff541d6 + // https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/bd8cd9f2-974f-4a9f-8e9c-e83001819942/iaudioclient-initialize-failure + + // initialization failed, try to use stereo waveformat + m_wfx->nChannels = 2; + m_wfx->nBlockAlign = (2 * m_wfx->wBitsPerSample) / 8; + m_wfx->nAvgBytesPerSec = m_wfx->nSamplesPerSec * m_wfx->nBlockAlign; + + hr = m_clAudio->Initialize(AUDCLNT_SHAREMODE_SHARED, m_port == PORT_OUTPUT ? AUDCLNT_STREAMFLAGS_LOOPBACK : 0, + hnsRequestedDuration, 0, m_wfx, NULL); + if (hr != S_OK) + { + // stereo waveformat didnt work either, throw an error + RmLog(LOG_WARNING, L"Failed to initialize audio client."); + } + } + EXIT_ON_ERROR(hr); + + // initialize the audio capture client + hr = m_clAudio->GetService(IID_IAudioCaptureClient, (void**)&m_clCapture); + if (hr != S_OK) + { + RmLog(LOG_WARNING, L"Failed to create audio capture client."); + } + EXIT_ON_ERROR(hr); + + // start the stream + hr = m_clAudio->Start(); + if (hr != S_OK) + { + RmLog(LOG_WARNING, L"Failed to start the stream."); + } + EXIT_ON_ERROR(hr); + + // initialize the watchdog timer + QueryPerformanceCounter(&m_pcFill); + + return S_OK; + +Exit: + DeviceRelease(); + return hr; +} + + +/** + * Release handles to audio resources. (except the enumerator) + */ +void Measure::DeviceRelease () +{ +#if (WINDOWS_BUG_WORKAROUND) + if (m_clBugAudio) + { + if (!m_parent) RmLog(LOG_DEBUG, L"Releasing dummy stream audio device."); + m_clBugAudio->Stop(); + } + SAFE_RELEASE(m_clBugRender); + SAFE_RELEASE(m_clBugAudio); +#endif + + if (m_clAudio) + { + if (!m_parent) RmLog(LOG_DEBUG, L"Releasing audio device."); + m_clAudio->Stop(); + } + + SAFE_RELEASE(m_clCapture); + + if (m_wfx) + { + CoTaskMemFree(m_wfx); + m_wfx = NULL; + } + + SAFE_RELEASE(m_clAudio); + SAFE_RELEASE(m_dev); + + for (int iChan = 0; iChan < Measure::MAX_CHANNELS; ++iChan) + { + if (m_fftCfg[iChan]) kiss_fftr_free(m_fftCfg[iChan]); + m_fftCfg[iChan] = NULL; + + if (m_fftIn[iChan]) free(m_fftIn[iChan]); + m_fftIn[iChan] = NULL; + + if (m_fftOut[iChan]) free(m_fftOut[iChan]); + m_fftOut[iChan] = NULL; + + if (m_bandOut[iChan]) free(m_bandOut[iChan]); + m_bandOut[iChan] = NULL; + + m_rms[iChan] = 0.0; + m_peak[iChan] = 0.0; + } + + if (m_bandFreq) + { + free(m_bandFreq); + m_bandFreq = NULL; + } + + if (m_fftTmpOut) + { + free(m_fftTmpOut); + free(m_fftTmpIn); + free(m_fftKWdw); + m_fftTmpOut = NULL; + m_fftTmpIn = NULL; + m_fftKWdw = NULL; + kiss_fft_cleanup(); + } + + m_devName[0] = '\0'; + m_format = FMT_INVALID; +}