Last active
August 20, 2018 15:24
-
-
Save yak1ex/069d950b2fb54bd29dc587854bd78ca6 to your computer and use it in GitHub Desktop.
MX FLOW
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <windows.h> | |
#include <Strsafe.h> | |
#include <vector> | |
#include <boost/align/aligned_allocator.hpp> | |
typedef boost::alignment::aligned_allocator<void, 4> aligned_allocator; | |
class buffer | |
{ | |
public: | |
buffer() : buf(aligned_allocator()) { buf.reserve(100); } | |
void align(std::size_t size) | |
{ | |
std::size_t pad = size - buf.size() % size; | |
if(pad != size) buf.insert(buf.end(), pad , 0); | |
} | |
template<typename T> | |
void add(const T& t) | |
{ | |
auto st = static_cast<const char*>(static_cast<const void*>(&t)); | |
buf.insert(buf.end(), st, st + sizeof(T)); | |
} | |
void add(LPCWSTR lp) | |
{ | |
std::size_t len = lstrlenW(lp); | |
auto st = static_cast<const char*>(static_cast<const void*>(lp)); | |
buf.insert(buf.end(), st, st+(len+1)*sizeof(WCHAR)); | |
} | |
void add(const std::wstring& w) | |
{ | |
auto st = static_cast<const char*>(static_cast<const void*>(w.data())); | |
buf.insert(buf.end(), st, st+(w.size()+1)*sizeof(WCHAR)); | |
} | |
const char* data() const { return buf.data(); } | |
std::size_t size() const { return buf.size(); } | |
private: | |
std::vector<char, aligned_allocator> buf; | |
}; | |
static POINT GetPos(POINT pt, const RECT &rcDlg, const RECT &rcWork) | |
{ | |
if(pt.x + rcDlg.right > rcWork.right) { | |
pt.x = rcWork.right - rcDlg.right; | |
} | |
if(pt.x < rcWork.left) { | |
pt.x = rcWork.left; | |
} | |
if(pt.y + rcDlg.bottom > rcWork.bottom) { | |
pt.y = rcWork.bottom - rcDlg.bottom; | |
} | |
if(pt.y < rcWork.top) { | |
pt.y = rcWork.top; | |
} | |
return pt; | |
} | |
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) | |
{ | |
switch(uMsg) { | |
case WM_INITDIALOG: { | |
POINT pt; | |
RECT rc; | |
if(GetWindowRect(hwndDlg, &rc) && GetCursorPos(&pt)) { | |
if(HMONITOR hm = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL)) { | |
MONITORINFO mi = { sizeof(MONITORINFO) }; | |
if(GetMonitorInfo(hm, &mi)) { | |
POINT ptPlace = GetPos(pt, rc, mi.rcWork); | |
SetWindowPos(hwndDlg, HWND_TOP, ptPlace.x, ptPlace.y, rc.right - rc.left, rc.bottom - rc.top, 0); | |
} | |
} | |
} | |
return TRUE; | |
} | |
case WM_COMMAND: | |
switch(LOWORD(wParam)) { | |
case IDOK: | |
EndDialog(hwndDlg, 0); | |
break; | |
} | |
break; | |
case WM_CLOSE: | |
EndDialog(hwndDlg, 0); | |
return TRUE; | |
} | |
return FALSE; | |
} | |
struct VerStr | |
{ | |
std::wstring wsProductName; | |
std::wstring wsProductVersion; | |
std::wstring wsFileDescription; | |
std::wstring wsLegalCopyright; | |
}; | |
static VerStr GetVerStr(HINSTANCE hInst) | |
{ | |
char buf[2048]; | |
GetModuleFileName(hInst, buf, sizeof(buf)); | |
DWORD dwSize = GetFileVersionInfoSize(buf, 0); | |
std::vector<char> v(dwSize); | |
GetFileVersionInfo(buf, 0, dwSize, v.data()); | |
struct LANGANDCODEPAGE { | |
WORD wLanguage; | |
WORD wCodePage; | |
} *lpTranslate; | |
UINT uiLen; | |
VerQueryValue(v.data(), "\\VarFileInfo\\Translation", (LPVOID*)&lpTranslate, &uiLen); | |
UINT uiIdx = 0, uiPrior = 0; | |
for(UINT i = 1; i < uiLen / sizeof(*lpTranslate); ++i) { | |
UINT uiTemp = 0; | |
if(lpTranslate[i].wLanguage == 0x0411 && lpTranslate[i].wCodePage == 0x03A4) { // JP, SJIS | |
uiTemp = 4; | |
} else if(lpTranslate[i].wLanguage == 0x0411) { // JP | |
uiTemp = 3; | |
} else if(lpTranslate[i].wLanguage == 0x0409 && lpTranslate[i].wCodePage == 0x04E4) { // ENG, ANSI | |
uiTemp = 2; | |
} else if(lpTranslate[i].wLanguage == 0x0409) { // ENG | |
uiTemp = 1; | |
} | |
if(uiTemp > uiPrior) { | |
uiTemp = uiPrior; | |
uiIdx = i; | |
} | |
} | |
WCHAR wbuf[128]; | |
LPWSTR lp; | |
UINT uiSize; | |
VerStr vsRet; | |
StringCbPrintfW(wbuf, sizeof(wbuf), L"\\StringFileInfo\\%04x%04x\\ProductName", lpTranslate[uiIdx].wLanguage, lpTranslate[uiIdx].wCodePage); | |
VerQueryValueW(v.data(), wbuf, (LPVOID*)&lp, &uiSize); | |
vsRet.wsProductName.assign(lp); | |
std::wstring wsProductName(lp); | |
StringCbPrintfW(wbuf, sizeof(wbuf), L"\\StringFileInfo\\%04x%04x\\ProductVersion", lpTranslate[uiIdx].wLanguage, lpTranslate[uiIdx].wCodePage); | |
VerQueryValueW(v.data(), wbuf, (LPVOID*)&lp, &uiSize); | |
vsRet.wsProductVersion.assign(lp); | |
StringCbPrintfW(wbuf, sizeof(wbuf), L"\\StringFileInfo\\%04x%04x\\FileDescription", lpTranslate[uiIdx].wLanguage, lpTranslate[uiIdx].wCodePage); | |
VerQueryValueW(v.data(), wbuf, (LPVOID*)&lp, &uiSize); | |
vsRet.wsFileDescription.assign(lp); | |
StringCbPrintfW(wbuf, sizeof(wbuf), L"\\StringFileInfo\\%04x%04x\\LegalCopyright", lpTranslate[uiIdx].wLanguage, lpTranslate[uiIdx].wCodePage); | |
VerQueryValueW(v.data(), wbuf, (LPVOID*)&lp, &uiSize); | |
vsRet.wsLegalCopyright.assign(lp); | |
return std::move(vsRet); | |
} | |
// TODO: size adjustment | |
static buffer MakeBuffer(const VerStr& vs) | |
{ | |
buffer buffer; | |
// DLGTEMPLATEEX | |
buffer.add<WORD>(1); // dlgVer | |
buffer.add<WORD>(0xFFFF); // signature | |
buffer.add<DWORD>(0); // helpID | |
buffer.add<DWORD>(0); // exStyle | |
buffer.add<DWORD>(DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU); | |
// style | |
buffer.add<WORD>(4); // cDlgItems | |
buffer.add<short>(0); // x | |
buffer.add<short>(0); // y | |
buffer.add<short>(140); // cx | |
buffer.add<short>(80); // cy | |
buffer.add<WORD>(0); // menu | |
buffer.add<WORD>(0); // windowClass | |
buffer.add(L"About"); // title | |
buffer.add<WORD>(8); // pointsize | |
buffer.add<WORD>(400); // weight | |
buffer.add<BYTE>(0); // italic | |
buffer.add<BYTE>(1); // charset | |
buffer.add(L"MS Shell Dlg"); | |
// typeface | |
// DLGITEMTEMPLATEEX | |
buffer.align(4); | |
buffer.add<DWORD>(0); // helpID | |
buffer.add<DWORD>(0); // exStyle | |
buffer.add<DWORD>(SS_LEFT | WS_VISIBLE); | |
// style | |
buffer.add<short>(10); // x | |
buffer.add<short>(10); // y | |
buffer.add<short>(120); // cx | |
buffer.add<short>(7); // cy | |
buffer.add<DWORD>(-1); // id | |
buffer.add<WORD>(0xFFFF); // windowClass | |
buffer.add<WORD>(0x0082); | |
buffer.add(vs.wsProductName + L" " + vs.wsProductVersion); | |
// title | |
buffer.add<WORD>(0); // extraCount | |
buffer.align(4); | |
buffer.add<DWORD>(0); // helpID | |
buffer.add<DWORD>(0); // exStyle | |
buffer.add<DWORD>(SS_LEFT | WS_VISIBLE); | |
// style | |
buffer.add<short>(10); // x | |
buffer.add<short>(30); // y | |
buffer.add<short>(120); // cx | |
buffer.add<short>(7); // cy | |
buffer.add<DWORD>(-1); // id | |
buffer.add<WORD>(0xFFFF); // windowClass | |
buffer.add<WORD>(0x0082); | |
buffer.add(vs.wsFileDescription); | |
// title | |
buffer.add<WORD>(0); // extraCount | |
buffer.align(4); | |
buffer.add<DWORD>(0); // helpID | |
buffer.add<DWORD>(0); // exStyle | |
buffer.add<DWORD>(SS_LEFT | WS_VISIBLE); | |
// style | |
buffer.add<short>(10); // x | |
buffer.add<short>(40); // y | |
buffer.add<short>(120); // cx | |
buffer.add<short>(7); // cy | |
buffer.add<DWORD>(-1); // id | |
buffer.add<WORD>(0xFFFF); // windowClass | |
buffer.add<WORD>(0x0082); | |
buffer.add(vs.wsLegalCopyright); | |
// title | |
buffer.add<WORD>(0); // extraCount | |
buffer.align(4); | |
buffer.add<DWORD>(0); // helpID | |
buffer.add<DWORD>(0); // exStyle | |
buffer.add<DWORD>(BS_DEFPUSHBUTTON | WS_TABSTOP | WS_VISIBLE); | |
// style | |
buffer.add<short>(45); // x | |
buffer.add<short>(60); // y | |
buffer.add<short>(50); // cx | |
buffer.add<short>(14); // cy | |
buffer.add<DWORD>(IDOK); // id | |
buffer.add<WORD>(0xFFFF); // windowClass | |
buffer.add<WORD>(0x0080); | |
buffer.add(L"OK"); // title | |
buffer.add<WORD>(0); // extraCount | |
return std::move(buffer); | |
} | |
// TODO: icon control | |
INT_PTR AboutDialogBox(HINSTANCE hInst, HWND hwndParent) | |
{ | |
auto vs = GetVerStr(hInst); | |
auto buf = MakeBuffer(vs); | |
DialogBoxIndirect(hInst, reinterpret_cast<LPCDLGTEMPLATE>(buf.data()), 0, DialogProc); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef ABOUTDLG_H | |
#define ABOUTDLG_H | |
INT_PTR AboutDialogBox(HINSTANCE hInst, HWND hwndParent); | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// client.cpp | |
// ~~~~~~~~~~ | |
// | |
// Copyright (c) 2003-2017 Christopher M. Kohlhoff (chris at kohlhoff dot com) | |
// | |
// Distributed under the Boost Software License, Version 1.0. (See accompanying | |
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
// | |
#include <iostream> | |
#include <boost/array.hpp> | |
#include <boost/asio.hpp> | |
using boost::asio::ip::udp; | |
int main(int argc, char* argv[]) | |
{ | |
if(argc < 1) return 1; | |
try | |
{ | |
boost::asio::io_service io_service; | |
udp::resolver resolver(io_service); | |
udp::resolver::query query(udp::v4(), argv[1], "59865"); | |
udp::endpoint receiver_endpoint = *resolver.resolve(query); | |
udp::socket socket(io_service); | |
socket.open(udp::v4()); | |
std::string send_buf(argc > 2 ? "6b01d_2de54084:1" : "6b01d_2de54084:0"); | |
socket.send_to(boost::asio::buffer(send_buf), receiver_endpoint); | |
boost::array<char, 128> recv_buf; | |
udp::endpoint sender_endpoint; | |
size_t len = socket.receive_from( | |
boost::asio::buffer(recv_buf), sender_endpoint); | |
std::cout.write(recv_buf.data(), len); | |
} | |
catch (std::exception& e) | |
{ | |
std::cerr << e.what() << std::endl; | |
} | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <fstream> | |
#include <string> | |
#include <array> | |
#include <regex> | |
#include <filesystem> | |
namespace fs = std::experimental::filesystem; | |
#include <boost/asio.hpp> | |
using boost::asio::ip::udp; | |
#include <windows.h> | |
#include <windowsx.h> | |
#include <shlobj.h> | |
#include <wtsapi32.h> | |
#include <Shellapi.h> | |
#include <Strsafe.h> | |
#include <cstdarg> | |
#include "notify.h" | |
#include "AboutDlg.h" | |
#include "FlowCompanion.rh" | |
// FIXME: tip state | |
// TODO: log window | |
enum class mode { | |
ENABLE, DISABLE, CANCEL | |
}; | |
char* to_string(mode m) | |
{ | |
switch(m) { | |
case mode::ENABLE: return "ENABLE"; | |
case mode::DISABLE: return "DISABLE"; | |
case mode::CANCEL: return "CANCEL"; | |
default: return ""; | |
} | |
} | |
mode last_enable_mode; | |
bool session_locked; | |
boost::asio::io_service io_service; | |
#define WM_NOTIFYFROMICON WM_APP | |
/////////////////////////////////////////////////////////////////////// | |
#ifndef NUM_OUTPUT_DEBUG_PRINTF_BUFFER | |
#define NUM_OUTPUT_DEBUG_PRINTF_BUFFER 2048 | |
#endif | |
inline void OutputDebugPrintf(LPCTSTR format, ...) | |
{ | |
std::va_list ap; | |
va_start(ap, format); | |
TCHAR buf[NUM_OUTPUT_DEBUG_PRINTF_BUFFER]; | |
StringCbVPrintf(buf, sizeof(buf), format, ap); | |
OutputDebugString(buf); | |
va_end(ap); | |
} | |
inline void OutputDebugVPrintf(LPCTSTR format, std::va_list ap) | |
{ | |
TCHAR buf[NUM_OUTPUT_DEBUG_PRINTF_BUFFER]; | |
StringCbVPrintf(buf, sizeof(buf), format, ap); | |
OutputDebugString(buf); | |
} | |
/////////////////////////////////////////////////////////////////////// | |
inline void Debug(const char *format, ...) | |
{ | |
std::va_list ap; | |
va_start(ap, format); | |
char buf[NUM_OUTPUT_DEBUG_PRINTF_BUFFER]; | |
StringCbPrintf(buf, sizeof(buf), "FLOW companion: %s", format); | |
OutputDebugVPrintf(buf, ap); | |
va_end(ap); | |
} | |
const int RESERVE_WIDTH = 200; | |
const int RESERVE_HEIGHT = 100; | |
const int MENU_OFFSET = 30; | |
void ToggleFlow() | |
{ | |
UINT mid = RegisterWindowMessage("Tray.{47BCDAC1-2E6F-4f9a-9A3F-68A3B97CE33E}"); | |
HWND hwnd = FindWindow("LOGI_LOGIOPTIONSMGR", "LogiOptionsMgr"); | |
if(hwnd) { | |
POINT pt; | |
if(GetCursorPos(&pt)) { | |
HMONITOR hm = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL); | |
if(hm) { | |
MONITORINFO mi = { sizeof(MONITORINFO) }; | |
GetMonitorInfo(hm, &mi); | |
Debug("(%d,%d) in (%d,%d)-(%d,%d) (%d,%d)-(%d,%d)", pt.x, pt.y, mi.rcWork.left, mi.rcWork.top, mi.rcWork.right, mi.rcWork.bottom, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right, mi.rcMonitor.bottom); | |
if(pt.x - RESERVE_WIDTH < mi.rcMonitor.left) { pt.x = mi.rcMonitor.left + RESERVE_WIDTH; } | |
if(pt.x + RESERVE_WIDTH > mi.rcMonitor.right) { pt.x = mi.rcMonitor.right - RESERVE_WIDTH; } | |
if(pt.y + RESERVE_HEIGHT > mi.rcMonitor.bottom) { pt.y = mi.rcMonitor.bottom - RESERVE_HEIGHT; } | |
SetCursorPos(pt.x, pt.y); | |
GetCursorPos(&pt); | |
Debug("(%d,%d) in (%d,%d)-(%d,%d) (%d,%d)-(%d,%d)", pt.x, pt.y, mi.rcWork.left, mi.rcWork.top, mi.rcWork.right, mi.rcWork.bottom, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right, mi.rcMonitor.bottom); | |
} else { | |
Debug("monitor null at (%d,%d)", pt.x, pt.y); | |
} | |
PostMessage(hwnd, mid, 1, WM_RBUTTONDOWN); | |
PostMessage(hwnd, mid, 1, WM_RBUTTONUP); | |
auto timer = std::make_shared<boost::asio::deadline_timer>(io_service, boost::posix_time::milliseconds(500)); | |
timer->async_wait([timer, hwnd, pt](const boost::system::error_code&){ | |
const unsigned int ptval = ((pt.y+MENU_OFFSET)<<16)+pt.x+MENU_OFFSET; | |
PostMessage(hwnd, WM_LBUTTONDOWN, MK_LBUTTON, ptval); | |
PostMessage(hwnd, WM_LBUTTONUP, 0, ptval); | |
}); | |
} else { // NOTE: failed in lock screen | |
Debug("GetCurPos() failed: %d", GetLastError()); | |
} | |
} | |
} | |
enum class STATE { UNKNOWN, ENABLED, DISABLED }; | |
fs::path GetConfPathInit() | |
{ | |
char buf[MAX_PATH]; | |
if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, buf))) { | |
std::string s(buf); | |
s += "\\LogiShrd\\LogiOptions\\devices"; | |
decltype(fs::last_write_time(std::declval<fs::path>())) ftCur{}; | |
fs::path pCur; | |
int nCount = 0; | |
for(auto p : fs::recursive_directory_iterator(s)) { | |
if(p.path().extension() == ".json") { | |
auto ft = fs::last_write_time(p); | |
if(ft > ftCur) { | |
ftCur = ft; | |
pCur = p.path(); | |
++nCount; | |
Debug("ConfPath %s", pCur.string().c_str()); | |
} | |
} | |
} | |
if(nCount == 0) { | |
NotifyError("FLOW companion", "No config found"); | |
} else if(nCount != 1) { | |
std::string s("Multiple config found, "); | |
s += pCur.filename().string(); | |
s += " is anyway used."; | |
NotifyWarn("FLOW companion", s.c_str()); | |
} | |
return pCur; | |
} else return {}; | |
} | |
fs::path& GetConfPath() | |
{ | |
static fs::path path = GetConfPathInit(); | |
return path; | |
} | |
STATE GetFlow() | |
{ | |
STATE st = STATE::UNKNOWN; | |
std::string s(GetConfPath().string()); | |
if(s.size()) { | |
std::ifstream ifs(s); | |
std::string line; | |
while(getline(ifs, line)) { | |
if(line.find("\"enabled\" : 0,") != std::string::npos) { | |
st = STATE::DISABLED; | |
} else if(line.find("\"enabled\" : 1,") != std::string::npos) { | |
st = STATE::ENABLED; | |
} | |
} | |
} | |
return st; | |
} | |
bool IsFlowMismatched(bool enable) | |
{ | |
auto st = GetFlow(); | |
return (st != STATE::UNKNOWN) && ((st == STATE::ENABLED) ^ enable); | |
} | |
bool SetFlow(bool enable) | |
{ | |
if(IsFlowMismatched(enable)) { | |
ToggleFlow(); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/////////////////////////////////////////////////////////////////////// | |
namespace detail { | |
template<typename T, typename FromThis> | |
struct shared_only_base | |
{ | |
using ReturnType = decltype(std::declval<FromThis>().shared_from_this()); | |
using ElementType = typename ReturnType::element_type; | |
using type = FromThis; | |
}; | |
template<typename T> | |
struct shared_only_base<T, void> | |
{ | |
using ReturnType = void; | |
using ElementType = void; | |
using type = struct {}; | |
}; | |
}; | |
template<typename T, typename FromThis = std::enable_shared_from_this<T>> | |
struct shared_only : detail::shared_only_base<T, FromThis>::type | |
{ | |
static_assert(std::is_same<FromThis, void>::value || std::is_convertible<typename detail::shared_only_base<T, FromThis>::ReturnType, std::shared_ptr<T>>::value, "mismatch types between smart pointer and enable_shared_from_this"); | |
static_assert(std::is_same<FromThis, void>::value || std::is_same<typename detail::shared_only_base<T, FromThis>::ElementType, T>::value, "mismatch types between element type and enable_shared_from_this"); | |
protected: | |
shared_only() = default; // prohibit construction without inheritance | |
template<typename ... U> | |
static std::shared_ptr<T> make(U&& ... u) | |
{ | |
return std::make_shared<impl>(std::forward<U>(u)...); | |
} | |
private: | |
struct impl : T | |
{ | |
// inheriting constructor keeps accessibility so that explicit definition is required | |
template<typename ... U> | |
impl(U&& ... u) : T(std::forward<U>(u)...) {} | |
}; | |
}; | |
/////////////////////////////////////////////////////////////////////// | |
struct enabler : shared_only<enabler> | |
{ | |
public: | |
void request() | |
{ | |
auto self(shared_from_this()); | |
if(IsFlowMismatched(enable)) { // necessary to switch | |
LASTINPUTINFO lii = { sizeof(LASTINPUTINFO) }; | |
GetLastInputInfo(&lii); | |
DWORD dwCur = GetTickCount(); | |
if(dwCur + (lii.dwTime > dwCur ? 0xFFFFFFFF : 0) - lii.dwTime > 3000) { // idle passed | |
if(SetFlow(enable)) { | |
timer.expires_from_now(boost::posix_time::milliseconds(1000)); | |
timer.async_wait([self,this](const boost::system::error_code &error) { | |
if(error != boost::asio::error::operation_aborted && alive) { | |
auto st = GetFlow(); | |
if((st != STATE::UNKNOWN) && ((st == STATE::ENABLED) ^ enable)) { | |
if(error != boost::asio::error::operation_aborted && alive) { | |
Debug("enabler: retry"); | |
request(); | |
} | |
} else { | |
if(enable) { | |
NotifyInfoWithStatus("FLOW companion", "MX flow has been enabled.", notify_icon::ICON::ON, "FLOW companion\n[enable]"); | |
Debug("enabler: true"); | |
} else { | |
NotifyInfoWithStatus("FLOW companion", "MX flow has been disabled.", notify_icon::ICON::OFF, "FLOW companion\n[disable]"); | |
Debug("enabler: false"); | |
} | |
} | |
} | |
}); | |
} | |
} else { // idle waiting | |
if(!shown) { | |
if(enable) { | |
NotifyInfoWithStatus("FLOW companion", "Counterpart has been unlocked.\nAfter input idle for 3 secs, MX flow will be enabled.", notify_icon::ICON::OFF2ON, "FLOW companion\n[waiting idle...]"); | |
} else { | |
NotifyInfoWithStatus("FLOW companion", "Counterpart has been locked.\nAfter input idle for 3 secs, MX flow will be disabled.", notify_icon::ICON::ON2OFF, "FLOW companion\n[waiting idle...]"); | |
} | |
shown = true; | |
} | |
Debug("enabler: wait"); | |
timer.expires_from_now(boost::posix_time::seconds(1)); | |
timer.async_wait([self,this](const boost::system::error_code& error){ | |
if(error != boost::asio::error::operation_aborted && alive) { | |
request(); | |
} | |
}); | |
} | |
} | |
} | |
mode rest() | |
{ | |
if(alive) { | |
alive = false; | |
return enable ? mode::ENABLE : mode::DISABLE; | |
} else { | |
return mode::CANCEL; | |
} | |
} | |
static std::shared_ptr<enabler> make(boost::asio::io_service& io_service, mode enable) | |
{ | |
static std::weak_ptr<enabler> last; | |
if(enable == mode::CANCEL) { | |
return last.lock(); | |
} else { | |
auto ret = shared_only<enabler>::make(io_service, enable == mode::ENABLE); | |
if(auto p = last.lock()) { p->alive = false; } | |
last = ret; | |
Debug("enabler: make %p", ret.get()); | |
return std::move(ret); | |
} | |
} | |
~enabler() { Debug("enabler: destruct %p", this); } | |
private: | |
friend class shared_only<enabler>; | |
enabler(boost::asio::io_service& io_service, bool enable) : alive(true), shown(false), enable(enable), timer(io_service) | |
{ | |
} | |
bool alive; | |
bool shown; | |
bool enable; | |
boost::asio::deadline_timer timer; | |
}; | |
class udp_server | |
{ | |
public: | |
// FIXME: Hardcoded port number | |
udp_server(boost::asio::io_service& io_service) | |
: socket_(io_service, udp::endpoint(udp::v4(), 59865)) | |
{ | |
do_receive(); | |
} | |
private: | |
void do_receive() | |
{ | |
socket_.async_receive_from( | |
boost::asio::buffer(recv_buffer_), remote_endpoint_, | |
[this](const boost::system::error_code &error, std::size_t bytes_transferred) { | |
if (!error || error == boost::asio::error::message_size) | |
{ | |
std::string command(recv_buffer_.data(), bytes_transferred); | |
Debug(command.c_str()); | |
bool valid; | |
mode req; | |
if(command == GetConfPath().stem().string() + ":1") { | |
valid = true; | |
req = mode::ENABLE; | |
} else if(command == GetConfPath().stem().string() + ":0") { | |
valid = true; | |
req = mode::DISABLE; | |
} else { | |
valid = false; | |
} | |
message = valid ? "ACK" : "NAK"; | |
if(valid) { | |
if(session_locked) { | |
if(auto p = enabler::make(io_service, mode::CANCEL)) { | |
p->rest(); | |
} | |
last_enable_mode = req; | |
Debug("suspend %s", to_string(last_enable_mode)); | |
} else { | |
enabler::make(io_service, req)->request(); | |
} | |
} | |
do_send(); | |
} else { | |
do_receive(); | |
} | |
}); | |
} | |
void do_send() | |
{ | |
socket_.async_send_to(boost::asio::buffer(message), remote_endpoint_, | |
[this](const boost::system::error_code &error, std::size_t bytes_transferred) { | |
do_receive(); | |
}); | |
} | |
udp::socket socket_; | |
udp::endpoint remote_endpoint_; | |
std::array<char, 20> recv_buffer_; | |
std::string message; | |
}; | |
// Currently, only 2 hosts are considered | |
std::string GetHost() | |
{ | |
std::string s(GetConfPath().string()); | |
if(s.size()) { | |
std::ifstream ifs(s); | |
std::string line; | |
std::regex regex_current("\"current_host\" : (\\d),"); | |
std::regex regex_hostidx("\"host_index\" : (\\d+),"); | |
std::regex regex_hostname("\"host_name\" : \"([^\"]+)\","); | |
std::smatch m; | |
int current_idx = -1; | |
bool is_target = false; | |
while(getline(ifs, line)) { | |
if(std::regex_search(line, m, regex_current)) { | |
current_idx = std::stoi(m[1]); | |
} else if(std::regex_search(line, m, regex_hostidx)) { | |
if(std::stoi(m[1]) != current_idx) is_target = true; | |
} else if(is_target && std::regex_search(line, m, regex_hostname)) { | |
return m[1]; | |
} | |
} | |
} | |
return {}; | |
} | |
struct notifier : shared_only<notifier> | |
{ | |
public: | |
void request() | |
{ | |
auto self(shared_from_this()); | |
send_buf = (GetConfPath().stem().string() + (enable ? ":1" : ":0")); | |
timer.expires_from_now(boost::posix_time::seconds(10)); | |
timer.async_wait([self,this](const boost::system::error_code& error){ | |
if(!error) { | |
socket.cancel(); | |
} | |
}); | |
socket.async_send_to(boost::asio::buffer(send_buf), endpoint, | |
[self,this](const boost::system::error_code& error, std::size_t bytes_transferred) { | |
if(error == boost::asio::error::operation_aborted) { | |
if(alive) request(); | |
} else { | |
timer.expires_from_now(boost::posix_time::seconds(10)); | |
timer.async_wait([self,this](const boost::system::error_code& error){ | |
if(!error) { | |
socket.cancel(); | |
} | |
}); | |
udp::endpoint recv_endpoint; | |
socket.async_receive_from(boost::asio::buffer(recv_buf), recv_endpoint, | |
[self,this](const boost::system::error_code& error, std::size_t bytes_transferred) { | |
if(error == boost::asio::error::operation_aborted) { | |
if(alive) request(); | |
} else { | |
timer.cancel(); | |
recv_buf.data()[bytes_transferred]=0; | |
Debug(recv_buf.data()); | |
} | |
} | |
); | |
} | |
}); | |
} | |
static std::shared_ptr<notifier> make(boost::asio::io_service& io_service, udp::endpoint endpoint, bool enable) { | |
static std::weak_ptr<notifier> last; | |
auto ret = shared_only<notifier>::make(io_service, endpoint, enable); | |
if(auto p = last.lock()) { p->alive = false; } | |
last = ret; | |
return std::move(ret); | |
} | |
private: | |
friend class shared_only<notifier>; | |
notifier(boost::asio::io_service& io_service, udp::endpoint endpoint, bool enable) : alive(true), enable(enable), socket(io_service, udp::v4()), endpoint(endpoint), timer(io_service) | |
{ | |
} | |
bool alive; | |
bool enable; | |
std::string send_buf; | |
std::array<char, 128> recv_buf; | |
udp::endpoint endpoint; | |
udp::socket socket; | |
boost::asio::deadline_timer timer; | |
}; | |
void SendEnable(bool enable) | |
{ | |
auto resolver = std::make_shared<udp::resolver>(io_service); | |
// FIXME: Hardcoded port number | |
udp::resolver::query query(udp::v4(), GetHost(), "59865"); | |
resolver->async_resolve(query, [resolver, enable](const boost::system::error_code& error, udp::resolver::iterator iterator) { | |
if(!error) { | |
notifier::make(io_service, *iterator, enable)->request(); | |
} else { | |
Debug("Can't resolve %s by %s", GetHost().c_str(), error.message().c_str()); | |
} | |
}); | |
} | |
struct window_data | |
{ | |
HINSTANCE hInst; | |
HICON hIcon[4]; | |
HMENU hMenu; | |
notify_icon ni; | |
}; | |
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) | |
{ | |
try { // It seems that this try block is crucial for stability even though no exception is thrown | |
auto pwd = reinterpret_cast<window_data*>(GetWindowLongPtr(hwnd, GWLP_USERDATA)); | |
switch(uMsg) { | |
case WM_CREATE: { | |
auto pcs = reinterpret_cast<CREATESTRUCT*>(lParam); | |
pwd = reinterpret_cast<window_data*>(pcs->lpCreateParams); | |
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pwd)); | |
WTSRegisterSessionNotification(hwnd, NOTIFY_FOR_THIS_SESSION); | |
bool enable = GetFlow() == STATE::ENABLED; | |
pwd->ni.add(hwnd, WM_NOTIFYFROMICON, enable ? notify_icon::ICON::ON : notify_icon::ICON::OFF, enable ? "FLOW companion\n[enable]" : "FLOW companion\n[disable]"); | |
SetTimer(hwnd, 0, 5000, 0); | |
break; | |
} | |
case WM_WTSSESSION_CHANGE: | |
switch(wParam) { | |
case WTS_SESSION_LOCK: | |
session_locked = true; | |
if(auto p = enabler::make(io_service, mode::CANCEL)) { | |
last_enable_mode = p->rest(); | |
} else { | |
last_enable_mode = mode::CANCEL; | |
} | |
Debug("WTS_SESSION_LOCK enable_mode %s", to_string(last_enable_mode)); | |
SendEnable(false); | |
break; | |
case WTS_SESSION_UNLOCK: | |
session_locked = false; | |
Debug("WTS_SESSION_UNLOCK"); | |
if(last_enable_mode != mode::CANCEL) { | |
enabler::make(io_service, last_enable_mode)->request(); | |
Debug("resume %s", to_string(last_enable_mode)); | |
last_enable_mode = mode::CANCEL; | |
} | |
SendEnable(true); | |
break; | |
} | |
break; | |
case WM_COMMAND: | |
switch(LOWORD(wParam)) { | |
case IDM_ABOUT: | |
AboutDialogBox(pwd->hInst, 0); | |
break; | |
case IDM_EXIT: | |
DestroyWindow(hwnd); | |
break; | |
} | |
break; | |
case WM_NOTIFYFROMICON: | |
switch(LOWORD(lParam)) { | |
case WM_RBUTTONUP: | |
HMENU hmenu = GetSubMenu(pwd->hMenu,0); | |
SetForegroundWindow(hwnd); | |
TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_TOPALIGN, GET_X_LPARAM(wParam), GET_Y_LPARAM(wParam), 0, hwnd, NULL); | |
PostMessage(hwnd, WM_NULL, 0, 0); | |
break; | |
} | |
break; | |
case WM_NOTIFYREQ: | |
NotifyHandle(&pwd->ni, lParam); | |
break; | |
case WM_TIMER: | |
// FIXME: This overwrites intermediate state | |
pwd->ni.icon((GetFlow() == STATE::ENABLED) ? notify_icon::ICON::ON : notify_icon::ICON::OFF); | |
return 0; | |
case WM_DESTROY: | |
pwd->ni.remove(); | |
KillTimer(hwnd, 0); | |
PostQuitMessage(0); | |
io_service.stop(); | |
break; | |
} | |
} | |
catch (std::exception& e) | |
{ | |
Debug("exception in wndproc: %s", e.what()); | |
} | |
catch (...) | |
{ | |
Debug("unknown exception in wndproc"); | |
} | |
return DefWindowProc(hwnd, uMsg, wParam, lParam); | |
} | |
/////////////////////////////////////////////////////////////////////// | |
template<int PAUSE_NUM = 3, int PROCESS_NUM = 10> | |
struct win32_message_loop | |
{ | |
win32_message_loop(boost::asio::io_service &ios) : ios(ios), process_count(0), pause_count(0) { | |
ios.post([this]{ process(); }); | |
} | |
~win32_message_loop() { Debug("win32_message_loop: destructor"); } | |
void process() { | |
MSG msg; | |
if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { | |
if(msg.message == WM_QUIT) return; | |
TranslateMessage(&msg); | |
DispatchMessage(&msg); | |
process_count = (process_count + 1) % PROCESS_NUM; | |
pause_count = 0; | |
} else { | |
process_count = 0; | |
} | |
if(process_count == 0) { | |
if(pause_count++ < PAUSE_NUM) { | |
YieldProcessor(); | |
} else { | |
pause_count = 0; | |
Sleep(1); | |
} | |
} | |
ios.post([this]{ process(); }); | |
} | |
int process_count, pause_count; | |
boost::asio::io_service &ios; | |
}; | |
/////////////////////////////////////////////////////////////////////// | |
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow) | |
{ | |
try | |
{ | |
Debug("Counterpart host %s", GetHost().c_str()); | |
udp_server server(io_service); | |
WNDCLASSEX wce = {}; | |
wce.cbSize = sizeof(WNDCLASSEX); | |
wce.style = CS_HREDRAW | CS_VREDRAW; | |
wce.lpfnWndProc = WindowProc; | |
wce.hInstance = hInst; | |
wce.lpszClassName = "DummyWindowClass"; | |
RegisterClassEx(&wce); | |
window_data wd; | |
wd.hInst = hInst; | |
wd.ni.init(hInst, { IDI_ON, IDI_OFF, IDI_ON2OFF, IDI_OFF2ON }); | |
wd.hMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDM_POPUP)); | |
last_enable_mode = mode::CANCEL; | |
session_locked = false; | |
HWND hwnd = CreateWindowEx(0, wce.lpszClassName, "Test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, HWND_MESSAGE, 0, hInst, static_cast<void*>(&wd)); | |
NotifySetTarget(hwnd); | |
win32_message_loop<> loop(io_service); | |
io_service.run(); | |
} | |
catch (std::exception& e) | |
{ | |
Debug("exception: %s", e.what()); | |
} | |
catch (...) | |
{ | |
Debug("unknown exception"); | |
} | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <windows.h> | |
#include "FlowCompanion.rh" | |
IDI_ON ICON DISCARDABLE "on.ico" | |
IDI_OFF ICON DISCARDABLE "off.ico" | |
IDI_ON2OFF ICON DISCARDABLE "on2off.ico" | |
IDI_OFF2ON ICON DISCARDABLE "off2on.ico" | |
IDM_POPUP MENU DISCARDABLE | |
BEGIN | |
POPUP "Popup" | |
BEGIN | |
MENUITEM "&About", IDM_ABOUT | |
MENUITEM SEPARATOR | |
MENUITEM "Exit", IDM_EXIT | |
END | |
END | |
IDV_VERSIONINFO VERSIONINFO | |
FILEVERSION 0,1,2018,119 | |
PRODUCTVERSION 0,1,2018,119 | |
FILEOS VOS_NT_WINDOWS32 | |
FILETYPE VFT_DLL | |
FILESUBTYPE VFT2_UNKNOWN | |
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK | |
FILEFLAGS 0x00000000 | |
BEGIN | |
BLOCK "StringFileInfo" | |
BEGIN | |
BLOCK "041103A4" | |
BEGIN | |
VALUE "CompanyName", "Yak!" | |
VALUE "FileDescription", "Lock detection utility for MX FLOW" | |
VALUE "FileVersion", "Ver 0.01 (2018/01/19)" | |
VALUE "InternalName", "FlowCompanion.exe" | |
VALUE "LegalCopyright", "Written by Yak!" | |
VALUE "OriginalFilename", "FlowCompanion.exe" | |
VALUE "ProductName", "MX FLOW companion" | |
VALUE "ProductVersion", "Ver 0.01 (2018/01/19)" | |
END | |
END | |
BLOCK "VarFileInfo" | |
BEGIN | |
VALUE "Translation", 0x0411, 0x03A4 | |
END | |
END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define IDI_ON 1 | |
#define IDI_OFF 2 | |
#define IDI_ON2OFF 3 | |
#define IDI_OFF2ON 4 | |
#define IDM_POPUP 1 | |
#define IDM_EXIT 2 | |
#define IDM_ABOUT 3 | |
#define IDV_VERSIONINFO 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
BOOST_PATH=C:\usr\local\boost\1_67_0 | |
BOOST_LIB=$(BOOST_PATH)\lib32-msvc-14.0 | |
OPT=/EHsc /Zi /MDd /D_WIN32_WINNT=0x0601 | |
all: FlowCompanion.exe FCtest.exe | |
FlowCompanion.exe: FlowCompanion.obj notify.obj AboutDlg.obj FlowCompanion.res | |
cl $(OPT) FlowCompanion.obj notify.obj AboutDlg.obj FlowCompanion.res user32.lib shell32.lib wtsapi32.lib version.lib /link /libpath:$(BOOST_LIB) | |
FlowCompanion.map: FlowCompanion.obj notify.obj AboutDlg.obj FlowCompanion.res | |
cl $(OPT) FlowCompanion.obj notify.obj AboutDlg.obj FlowCompanion.res user32.lib shell32.lib wtsapi32.lib version.lib /link /libpath:$(BOOST_LIB) /map:FlowComparison.map | |
FlowCompanion.obj: FlowCompanion.cpp notify.h FlowCompanion.rh | |
cl $(OPT) /I $(BOOST_PATH) /c FlowCompanion.cpp | |
notify.obj: notify.cpp notify.h | |
cl $(OPT) /c notify.cpp | |
AboutDlg.obj: AboutDlg.cpp AboutDlg.h | |
cl $(OPT) /I $(BOOST_PATH) /c AboutDlg.cpp | |
FlowCompanion.res: FlowCompanion.rc FlowCompanion.rh on.ico off.ico on2off.ico off2on.ico | |
rc FlowCompanion.rc | |
FCtest.exe: | |
cl $(OPT) /I c:\usr\local\boost /EHsc FCtest.cpp /link /libpath:c:\usr\local\boost\lib32-msvc-14.0 | |
clean: | |
del *.obj *.exe *.res |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <windows.h> | |
#include "notify.h" | |
static HWND hwndMain; | |
struct notify_req | |
{ | |
enum class info_type | |
{ | |
ERR, WARN, INFO, NONE | |
} info; | |
const char* szTitle; | |
const char* szInfo; | |
bool status; | |
notify_icon::ICON icon_id; | |
const char* szStatus; | |
}; | |
void NotifySetTarget(HWND hwnd) | |
{ | |
hwndMain = hwnd; | |
} | |
void NotifyError(const char* szTitle, const char* szInfo) | |
{ | |
notify_req req = { | |
notify_req::info_type::ERR, | |
szTitle, | |
szInfo, | |
false | |
}; | |
SendMessage(hwndMain, WM_NOTIFYREQ, 0, reinterpret_cast<LPARAM>(&req)); | |
} | |
void NotifyWarn(const char* szTitle, const char* szInfo) | |
{ | |
notify_req req = { | |
notify_req::info_type::WARN, | |
szTitle, | |
szInfo, | |
false | |
}; | |
SendMessage(hwndMain, WM_NOTIFYREQ, 0, reinterpret_cast<LPARAM>(&req)); | |
} | |
void NotifyInfo(const char* szTitle, const char* szInfo) | |
{ | |
notify_req req = { | |
notify_req::info_type::INFO, | |
szTitle, | |
szInfo, | |
false | |
}; | |
SendMessage(hwndMain, WM_NOTIFYREQ, 0, reinterpret_cast<LPARAM>(&req)); | |
} | |
void NotifyInfoWithStatus(const char* szTitle, const char* szInfo, notify_icon::ICON icon_id, const char* szStatus) | |
{ | |
notify_req req = { | |
notify_req::info_type::INFO, | |
szTitle, | |
szInfo, | |
true, | |
icon_id, | |
szStatus | |
}; | |
SendMessage(hwndMain, WM_NOTIFYREQ, 0, reinterpret_cast<LPARAM>(&req)); | |
} | |
void NotifyStatus(notify_icon::ICON icon_id, const char* szStatus) | |
{ | |
notify_req req = { | |
notify_req::info_type::NONE, | |
nullptr, | |
nullptr, | |
true, | |
icon_id, | |
szStatus | |
}; | |
SendMessage(hwndMain, WM_NOTIFYREQ, 0, reinterpret_cast<LPARAM>(&req)); | |
} | |
void NotifyHandle(notify_icon* pni, LPARAM req_) | |
{ | |
auto req = reinterpret_cast<notify_req*>(req_); | |
switch(req->info) { | |
case notify_req::info_type::ERR: | |
pni->error(req->szTitle, req->szInfo); | |
break; | |
case notify_req::info_type::WARN: | |
pni->warn(req->szTitle, req->szInfo); | |
break; | |
case notify_req::info_type::INFO: | |
if(req->status) { | |
pni->info_with_status(req->szTitle, req->szInfo, req->icon_id, req->szStatus); | |
} else { | |
pni->info(req->szTitle, req->szInfo); | |
} | |
break; | |
case notify_req::info_type::NONE: | |
pni->status(req->icon_id, req->szStatus); | |
break; | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef NOTIFY_H | |
#define NOTIFY_H | |
#include <utility> | |
#include <algorithm> | |
#define WM_NOTIFYREQ (WM_APP+1) | |
class notify_icon | |
{ | |
public: | |
enum class ICON { | |
ON, OFF, ON2OFF, OFF2ON, NUM | |
}; | |
notify_icon() | |
{ | |
ZeroMemory(&nid, sizeof(nid)); | |
nid.cbSize = sizeof(nid); | |
} | |
void init(HINSTANCE hInst, std::initializer_list<int> l) | |
{ | |
std::transform(l.begin(), l.end(), std::begin(hIcon), [hInst](int n){ return LoadIcon(hInst, MAKEINTRESOURCE(n)); }); | |
} | |
void add(HWND hwnd, UINT uMsg, ICON icon, const char* szTip) | |
{ | |
nid.hWnd = hwnd; | |
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP; | |
nid.uCallbackMessage = uMsg; | |
nid.hIcon = update_icon(icon); | |
lstrcpy(nid.szTip, szTip); | |
nid.uVersion = NOTIFYICON_VERSION_4; | |
Shell_NotifyIcon(NIM_ADD, &nid); | |
Shell_NotifyIcon(NIM_SETVERSION, &nid); | |
} | |
void icon(ICON icon) | |
{ | |
nid.uFlags = NIF_ICON | NIF_TIP | NIF_SHOWTIP; | |
nid.hIcon = update_icon(icon); | |
Shell_NotifyIcon(NIM_MODIFY, &nid); | |
iCur = icon; | |
} | |
void status(ICON icon, const char* szTip) | |
{ | |
nid.uFlags = NIF_ICON | NIF_TIP | NIF_SHOWTIP; | |
nid.hIcon = update_icon(icon); | |
lstrcpy(nid.szTip, szTip); | |
Shell_NotifyIcon(NIM_MODIFY, &nid); | |
iCur = icon; | |
} | |
void error(const char* szTitle, const char* szInfo) | |
{ | |
show_info(info_type::ERR, szTitle, szInfo); | |
} | |
void warn(const char* szTitle, const char* szInfo) | |
{ | |
show_info(info_type::WARN, szTitle, szInfo); | |
} | |
void info(const char* szTitle, const char* szInfo) | |
{ | |
show_info(info_type::INFO, szTitle, szInfo); | |
} | |
void info_with_status(const char* szTitle, const char* szInfo, ICON icon, const char* szTip) | |
{ | |
nid.uFlags = NIF_INFO | NIF_ICON | NIF_TIP | NIF_SHOWTIP; | |
lstrcpy(nid.szInfoTitle, szTitle); | |
lstrcpy(nid.szInfo, szInfo); | |
nid.dwInfoFlags = NIIF_INFO; | |
nid.uTimeout = 5000; | |
nid.hIcon = update_icon(icon); | |
lstrcpy(nid.szTip, szTip); | |
Shell_NotifyIcon(NIM_MODIFY, &nid); | |
iCur = icon; | |
} | |
void remove() | |
{ | |
Shell_NotifyIcon(NIM_DELETE, &nid); | |
} | |
private: | |
enum class info_type | |
{ | |
ERR, WARN, INFO | |
}; | |
void show_info(info_type type, const char* szTitle, const char *szInfo) | |
{ | |
nid.uFlags = NIF_INFO | NIF_TIP | NIF_SHOWTIP; | |
lstrcpy(nid.szInfoTitle, szTitle); | |
lstrcpy(nid.szInfo, szInfo); | |
nid.dwInfoFlags = type == info_type::ERR ? NIIF_ERROR : type == info_type::WARN ? NIIF_WARNING : NIIF_INFO; | |
nid.uTimeout = 5000; | |
Shell_NotifyIcon(NIM_MODIFY, &nid); | |
} | |
HICON update_icon(ICON iReq) | |
{ | |
if((iCur == ICON::ON2OFF && iReq == ICON::ON) || | |
(iCur == ICON::OFF2ON && iReq == ICON::OFF) || | |
(iCur == ICON::ON && iReq == ICON::OFF2ON) || | |
(iCur == ICON::OFF && iReq == ICON::ON2OFF)) { | |
} else { | |
iCur = iReq; | |
} | |
return hIcon[static_cast<int>(iCur)]; | |
} | |
NOTIFYICONDATA nid; | |
HICON hIcon[ICON::NUM]; | |
ICON iCur; | |
}; | |
void NotifySetTarget(HWND hwnd); | |
void NotifyError(const char* szTitle, const char* szInfo); | |
void NotifyWarn(const char* szTitle, const char* szInfo); | |
void NotifyInfo(const char* szTitle, const char* szInfo); | |
void NotifyInfoWithStatus(const char* szTitle, const char* szInfo, notify_icon::ICON icon_id, const char* szStatus); | |
void NotifyStatus(int icon_id, const char* szStatus); | |
void NotifyHandle(notify_icon* pni, LPARAM req_); | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment