├── third_party └── ccronexpr │ ├── .gitignore │ ├── LICENSE │ ├── ccronexpr.h │ ├── README.md │ └── ccronexpr_test.c ├── CommandTrayHost ├── shutdownblock.h ├── updater.h ├── filewatcher.h ├── small.ico ├── taskbar.ico ├── CommandTrayHost.ico ├── targetver.h ├── language.h ├── admin_singleton.h ├── cron.h ├── CommandTrayHost.exe.manifest ├── resource.h ├── shutdownblock.cpp ├── stdafx.cpp ├── CommandTrayHost.h ├── configure.h ├── language_data.h ├── test.hpp ├── stdafx.h ├── filewatcher.cpp ├── utils.hpp ├── CommandTrayHost.rc ├── CommandTrayHost.vcxproj.filters ├── cache.h ├── language.cpp ├── cron.cpp ├── cache.cpp ├── updater.cpp ├── admin_singleton.cpp ├── CommandTrayHost.vcxproj └── utils.cpp ├── vcpkg_cache.txt ├── LICENSE ├── add_gitutf16.py ├── vcpkg_patch.py ├── CommandTrayHost.sln ├── appveyor.yml ├── .gitattributes ├── .gitignore ├── version_set.py ├── README.zh-cn.md └── README.md /third_party/ccronexpr/.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /nbproject 3 | -------------------------------------------------------------------------------- /CommandTrayHost/shutdownblock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | BOOL CreateShutdownHook(HANDLE &hThread); 4 | -------------------------------------------------------------------------------- /CommandTrayHost/updater.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | BOOL UpdaterChecker(PWSTR lpDir, HANDLE &hThread); 4 | -------------------------------------------------------------------------------- /CommandTrayHost/filewatcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | BOOL CreateFileWatch(PWSTR lpDir, HANDLE &hThread); 4 | -------------------------------------------------------------------------------- /CommandTrayHost/small.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rexdf/CommandTrayHost/HEAD/CommandTrayHost/small.ico -------------------------------------------------------------------------------- /CommandTrayHost/taskbar.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rexdf/CommandTrayHost/HEAD/CommandTrayHost/taskbar.ico -------------------------------------------------------------------------------- /CommandTrayHost/CommandTrayHost.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rexdf/CommandTrayHost/HEAD/CommandTrayHost/CommandTrayHost.ico -------------------------------------------------------------------------------- /vcpkg_cache.txt: -------------------------------------------------------------------------------- 1 | 8/30/2018 [nlohmann-json] update to 3.2.0 2 | 3 | 3/26/2018 [nlohmann-json] update to 3.1.2 4 | 5 | 2/2/2018 [nlohmann-json] update to 3.1.0 6 | 7 | 12/31/2017 [nlohmann-json] update to 3.0.1 8 | 9 | [nlohmann-json] Update to 3.0.0 -------------------------------------------------------------------------------- /CommandTrayHost/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /CommandTrayHost/language.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void update_isZHCN(bool); 4 | void update_locale_name_by_alias(); 5 | void update_locale_name_by_system(); 6 | 7 | void initialize_local(bool has_lang, PCSTR lang_str); 8 | 9 | // std::string translate(std::string); 10 | 11 | //std::wstring translate_w2w(const std::wstring&); 12 | std::string translate(const std::string&); 13 | -------------------------------------------------------------------------------- /CommandTrayHost/admin_singleton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | bool init_cth_path(); 4 | 5 | BOOL IsMyProgramRegisteredForStartup(PCWSTR); 6 | BOOL DisableStartUp(); 7 | BOOL EnableStartup(); 8 | void ElevateNow(); 9 | void RestartNow(); 10 | //void makeSingleInstance(); 11 | //void delete_lockfile(); 12 | void makeSingleInstance3(); 13 | 14 | void check_admin(/*bool*/); 15 | bool check_runas_admin(); 16 | -------------------------------------------------------------------------------- /CommandTrayHost/cron.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //void to_json(nlohmann::json& j, const cron_expr& p); 4 | 5 | //void from_json(const nlohmann::json& j, cron_expr& p); 6 | 7 | void crontab_log(const nlohmann::json& jsp_crontab_config, time_t, time_t, PCSTR, PCSTR, PCSTR, int, int); 8 | 9 | cron_expr* get_cron_expr(const nlohmann::json& jsp, cron_expr& result); 10 | 11 | 12 | void handle_crontab(size_t idx); 13 | -------------------------------------------------------------------------------- /third_party/ccronexpr/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015, staticlibs.net 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /CommandTrayHost/CommandTrayHost.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2,PerMonitor 6 | 7 | 8 | true 9 | 10 | 11 | -------------------------------------------------------------------------------- /CommandTrayHost/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by CommandTrayHost.rc 4 | // 5 | 6 | #define IDS_CMDLINE 1 7 | #define IDS_ENVIRONMENT 2 8 | #define IDS_PROXYLIST 3 9 | #define IDS_RASPBK 4 10 | #define IDI_ICON1 101 11 | #define IDI_ICON2 104 12 | #define IDI_TASKBAR 105 13 | #define IDI_SMALL 106 14 | 15 | #ifndef IDC_STATIC 16 | #define IDC_STATIC -1 17 | #endif 18 | // Next default values for new objects 19 | // 20 | #ifdef APSTUDIO_INVOKED 21 | #ifndef APSTUDIO_READONLY_SYMBOLS 22 | 23 | #define _APS_NO_MFC 130 24 | #define _APS_NEXT_RESOURCE_VALUE 129 25 | #define _APS_NEXT_COMMAND_VALUE 32771 26 | #define _APS_NEXT_CONTROL_VALUE 1000 27 | #define _APS_NEXT_SYMED_VALUE 110 28 | #endif 29 | #endif 30 | -------------------------------------------------------------------------------- /CommandTrayHost/shutdownblock.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "shutdownblock.h" 3 | #include "CommandTrayHost.h" 4 | #include "configure.h" 5 | 6 | //void WatchDirectory(LPTSTR lpDir) 7 | DWORD WINAPI ShutdownCleanUp(LPVOID lpParam) 8 | { 9 | kill_all(); 10 | #ifdef _DEBUG 11 | { 12 | //msg_prompt(L"Done!", L"Shutdown"); 13 | //Sleep(20000); 14 | std::ofstream o("finished_killall.txt", std::ios_base::app); 15 | o << " ok! " << std::endl; 16 | } 17 | #endif 18 | return 0; 19 | } 20 | 21 | BOOL CreateShutdownHook(HANDLE &hThread) 22 | { 23 | hThread = CreateThread(NULL, // default security attributes 24 | 0, // use default stack size 25 | (LPTHREAD_START_ROUTINE)ShutdownCleanUp, // thread function 26 | NULL, // no thread function argument 27 | 0, // use default creation flags 28 | NULL); 29 | if (hThread == NULL) 30 | { 31 | return FALSE; 32 | } 33 | return TRUE; 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 rexdf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /add_gitutf16.py: -------------------------------------------------------------------------------- 1 | # /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import re 6 | import sys 7 | import codecs 8 | import traceback 9 | 10 | UTF16_GITCONFIG = '''[filter "utf16"] 11 | clean = iconv -f utf-16le -t utf-8 12 | smudge = iconv -f utf-8 -t utf-16le 13 | required 14 | ''' 15 | 16 | 17 | def main(): 18 | git_config_path = os.path.expandvars(r'%USERPROFILE%\.gitconfig') 19 | if os.path.isfile(git_config_path): 20 | with open(git_config_path, "rb") as f: 21 | content = f.read().decode('utf-8').replace('\r\n', '\n') 22 | else: 23 | print(git_config_path, "not exist") 24 | content = '' 25 | if UTF16_GITCONFIG not in content: 26 | print(f"No UTF16_GITCONFIG in {git_config_path}") 27 | content = content + '\n' + UTF16_GITCONFIG if content else UTF16_GITCONFIG 28 | content = content.replace('\n', '\r\n').encode('utf-8') 29 | with open(git_config_path, "wb") as f: 30 | f.write(content) 31 | print("changed .gitconfig\n", content.decode('utf-8')) 32 | else: 33 | print('.gitconfig already includes [filter "utf16"]') 34 | 35 | if __name__ == '__main__': 36 | print(sys.version_info) 37 | if (sys.version_info < (3, 0)): 38 | sys.exit(2) 39 | main() 40 | -------------------------------------------------------------------------------- /vcpkg_patch.py: -------------------------------------------------------------------------------- 1 | # /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import re 6 | import sys 7 | import codecs 8 | import traceback 9 | import itertools 10 | 11 | 12 | def type_name_yeild(): 13 | for itm in itertools.product(('uint', 'int'), ('64', '32', '16', '8')): 14 | yield ''.join(itm) + '_t' 15 | for itm in ('float', 'double', 'size_t'): 16 | yield itm 17 | 18 | 19 | def main(): 20 | vcpkg_path = 'c:\\tools\\vcpkg\\installed' 21 | for folder in ('x64-windows', 'x86-windows'): 22 | for file_to_patch in ('include\\nlohmann\\json.hpp', 'include\\rapidjson\\document.h'): 23 | nlohmann_json_path = os.path.join( 24 | vcpkg_path, folder, file_to_patch) 25 | if os.path.isfile(nlohmann_json_path): 26 | with open(nlohmann_json_path, "rb") as f: 27 | content = f.read().decode('utf-8') 28 | print('\n'.join(content.split("\n")[:6])) 29 | content2 = content 30 | 31 | for type_name in type_name_yeild(): 32 | for fuc in ('max', 'min'): 33 | content2 = content2.replace(f'std::numeric_limits<{type_name}>::{fuc}(', 34 | f'(std::numeric_limits<{type_name}>::{fuc})(' 35 | ) 36 | 37 | with open(nlohmann_json_path, "wb") as f: 38 | f.write(content2.encode('utf-8')) 39 | 40 | print(nlohmann_json_path) 41 | print( 42 | f'{len(content)} --> {len(content2)} diff/2: {(len(content2)-len(content))/2}') 43 | else: 44 | print(nlohmann_json_path, " not exist!") 45 | 46 | 47 | if __name__ == '__main__': 48 | print(sys.version_info) 49 | if sys.version_info < (3, 0): 50 | sys.exit(2) 51 | try: 52 | main() 53 | except: 54 | traceback.print_exc() 55 | -------------------------------------------------------------------------------- /CommandTrayHost.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2010 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommandTrayHost", "CommandTrayHost\CommandTrayHost.vcxproj", "{18FC42B7-89B4-4972-9D65-C4322F6CC817}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | XP-Release|x64 = XP-Release|x64 15 | XP-Release|x86 = XP-Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {18FC42B7-89B4-4972-9D65-C4322F6CC817}.Debug|x64.ActiveCfg = Debug|x64 19 | {18FC42B7-89B4-4972-9D65-C4322F6CC817}.Debug|x64.Build.0 = Debug|x64 20 | {18FC42B7-89B4-4972-9D65-C4322F6CC817}.Debug|x86.ActiveCfg = Debug|Win32 21 | {18FC42B7-89B4-4972-9D65-C4322F6CC817}.Debug|x86.Build.0 = Debug|Win32 22 | {18FC42B7-89B4-4972-9D65-C4322F6CC817}.Release|x64.ActiveCfg = Release|x64 23 | {18FC42B7-89B4-4972-9D65-C4322F6CC817}.Release|x64.Build.0 = Release|x64 24 | {18FC42B7-89B4-4972-9D65-C4322F6CC817}.Release|x86.ActiveCfg = Release|Win32 25 | {18FC42B7-89B4-4972-9D65-C4322F6CC817}.Release|x86.Build.0 = Release|Win32 26 | {18FC42B7-89B4-4972-9D65-C4322F6CC817}.XP-Release|x64.ActiveCfg = XP-Release|x64 27 | {18FC42B7-89B4-4972-9D65-C4322F6CC817}.XP-Release|x64.Build.0 = XP-Release|x64 28 | {18FC42B7-89B4-4972-9D65-C4322F6CC817}.XP-Release|x86.ActiveCfg = XP-Release|Win32 29 | {18FC42B7-89B4-4972-9D65-C4322F6CC817}.XP-Release|x86.Build.0 = XP-Release|Win32 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {CC26772A-ACCA-49BD-8E1D-7504B3A67132} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /CommandTrayHost/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // CommandTrayHost.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | //#pragma comment( lib, "wininet" ) 10 | #pragma comment(lib, "urlmon.lib") 11 | #pragma comment(lib, "Shlwapi") 12 | 13 | #if VER_PRODUCTBUILD == 7600 14 | #pragma comment(lib, "Psapi") 15 | #endif 16 | 17 | #ifdef _DEBUG 18 | void log_message(PCSTR caller_filename, PCSTR caller_function, int line_number, PCWSTR pszFormat, ...) 19 | { 20 | // Expression: ("Buffer too small", 0) 21 | const size_t len = 2048 * 16; // *2048; 22 | static wchar_t s_acBuf[len]; // this here is a caveat! 23 | //wchar_t* s_acBuf = new wchar_t[len]; 24 | int len_caller = swprintf_s(s_acBuf, len, L"%S(%d) : [ %S ]\n", caller_filename, line_number, caller_function); 25 | if (len_caller >= 0) 26 | { 27 | va_list args; 28 | va_start(args, pszFormat); 29 | vswprintf_s(s_acBuf + len_caller, len - len_caller, pszFormat, args); 30 | OutputDebugString(s_acBuf); 31 | va_end(args); 32 | } 33 | else 34 | { 35 | assert(false); 36 | } 37 | //delete[]s_acBuf; 38 | } 39 | /* 40 | #include "configure.h" 41 | void log_message(PCSTR caller_filename, PCSTR caller_function, int line_number, PCSTR pszFormat, ...) 42 | { 43 | // Expression: ("Buffer too small", 0) 44 | const size_t len = 2048 * 16; 45 | static wchar_t s_acBuf[len]; // this here is a caveat! 46 | int len_caller = swprintf_s(s_acBuf, len, L"%S\n[%S]:#%d, ", caller_filename, caller_function, line_number); 47 | if (len_caller >= 0) 48 | { 49 | va_list args; 50 | va_start(args, pszFormat); 51 | vswprintf_s(s_acBuf + len_caller, len - len_caller, utf8_to_wstring(pszFormat).c_str(), args); 52 | OutputDebugString(s_acBuf); 53 | va_end(args); 54 | } 55 | else 56 | { 57 | assert(false); 58 | } 59 | }*/ 60 | #endif 61 | -------------------------------------------------------------------------------- /CommandTrayHost/CommandTrayHost.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "resource.h" 4 | 5 | BOOL DeleteTrayIcon(); 6 | BOOL ShowTrayIcon(LPCWSTR lpszProxy, DWORD dwMessage); 7 | 8 | #define NID_UID 666 9 | #define WM_TASKBARNOTIFY (WM_USER+0x20) 10 | //#define WM_TASKBARNOTIFY_MENUITEM_SHOW (WM_USER + 0x21) 11 | //#define WM_TASKBARNOTIFY_MENUITEM_HIDE (WM_USER + 0x22) 12 | //#define WM_TASKBARNOTIFY_MENUITEM_RELOAD (WM_USER + 0x23) 13 | #define WM_TASKBARNOTIFY_MENUITEM_ABOUT (WM_USER + 0x24) 14 | #define WM_TASKBARNOTIFY_MENUITEM_EXIT (WM_USER + 0x25) 15 | #define WM_TASKBARNOTIFY_MENUITEM_PROXYLIST_BASE (WM_USER + 0x26) 16 | 17 | #define WM_TASKBARNOTIFY_MENUITEM_UPDATE (WM_USER + 0x27) 18 | 19 | #define WM_TASKBARNOTIFY_MENUITEM_STARTUP (WM_USER + 0x10) 20 | #define WM_TASKBARNOTIFY_MENUITEM_OPENURL (WM_USER + 0x11) 21 | #define WM_TASKBARNOTIFY_MENUITEM_ELEVATE (WM_USER + 0x12) 22 | #define WM_TASKBARNOTIFY_MENUITEM_HIDEALL (WM_USER + 0x13) 23 | #define WM_TASKBARNOTIFY_MENUITEM_DISABLEALL (WM_USER + 0x14) 24 | #define WM_TASKBARNOTIFY_MENUITEM_ENABLEALL (WM_USER + 0x15) 25 | #define WM_TASKBARNOTIFY_MENUITEM_SHOWALL (WM_USER + 0x16) 26 | #define WM_TASKBARNOTIFY_MENUITEM_RESTARTALL (WM_USER + 0x17) 27 | #define WM_TASKBARNOTIFY_MENUITEM_CHECK_CACHEVALID (WM_USER + 0x18) 28 | #define WM_TASKBARNOTIFY_MENUITEM_FORCE_RESTART (WM_USER + 0x19) 29 | 30 | #define WM_DOCKED_ID_BASE (WM_USER + 0x300) 31 | #define WM_DOCKED_ID_END (WM_USER + 0x4300) 32 | 33 | // WM_TASKBARNOTIFY_MENUITEM_COMMAND_BASE+10*i 是一级菜单 显示名称 34 | // WM_TASKBARNOTIFY_MENUITEM_COMMAND_BASE+10*i+j 是二级菜单 显示命令 显示/隐藏 重启命令 启用/禁用 35 | #define WM_TASKBARNOTIFY_MENUITEM_COMMAND_BASE (WM_APP + 0x50) 36 | #define WM_APP_END 0xBFFF 37 | 38 | #define WM_HOTKEY_ID_BASE (WM_USER + 0x60) 39 | #define WM_HOTKEY_LEFT_CLICK (WM_USER + 0x61) 40 | #define WM_HOTKEY_RIGHT_CLICK (WM_USER + 0x62) 41 | #define WM_HOTKEY_ADD_ALPHA (WM_USER + 0x63) 42 | #define WM_HOTKEY_MINUS_ALPHA (WM_USER + 0x64) 43 | #define WM_HOTKEY_TOPMOST (WM_USER + 0x65) 44 | #define WM_HOTKEY_HIDE (WM_USER + 0x66) 45 | #define WM_HOTKEY_SHOWALL (WM_USER + 0x67) 46 | 47 | #define VM_TIMER_CREATEPROCESS_SHOW 0x100 48 | #define VM_TIMER_BASE 0x200 49 | 50 | #define CRONTAB_RENEW_MARKER (20*24*3600) 51 | #define CRONTAB_MAXIUM_SECONDS (USER_TIMER_MAXIMUM/1000 - 24 * 3600) 52 | -------------------------------------------------------------------------------- /CommandTrayHost/configure.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum ConfigMenuNameIndex 4 | { 5 | mShow, 6 | mHide, 7 | mEnable, 8 | mDisable, 9 | mRestart, 10 | mRunAsAdministrator, 11 | }; 12 | enum GloabalMenuNameIndx 13 | { 14 | mDisableAll, // order is important, commandtrayhost marked word: 4bfsza3ay 15 | mEnableAll, 16 | mHideAll, 17 | mShowall, 18 | mRestartALL, 19 | mElevate, 20 | mExit, 21 | 22 | mAll, 23 | mStartOnBoot, 24 | mHome, 25 | mAbout, 26 | mHelp, 27 | mUpdate, 28 | mDocked, 29 | }; 30 | 31 | const int hotkey_ids_global_section[] = { 32 | WM_TASKBARNOTIFY_MENUITEM_DISABLEALL , // order is important, commandtrayhost marked word: 4bfsza3ay 33 | WM_TASKBARNOTIFY_MENUITEM_ENABLEALL, 34 | WM_TASKBARNOTIFY_MENUITEM_HIDEALL, 35 | WM_TASKBARNOTIFY_MENUITEM_SHOWALL, 36 | WM_TASKBARNOTIFY_MENUITEM_RESTARTALL, 37 | WM_TASKBARNOTIFY_MENUITEM_ELEVATE, 38 | WM_TASKBARNOTIFY_MENUITEM_EXIT, 39 | WM_HOTKEY_LEFT_CLICK, //left click 40 | WM_HOTKEY_RIGHT_CLICK, //right click 41 | WM_HOTKEY_ADD_ALPHA, 42 | WM_HOTKEY_MINUS_ALPHA, 43 | WM_HOTKEY_TOPMOST, 44 | WM_HOTKEY_HIDE, 45 | WM_HOTKEY_SHOWALL, 46 | }; 47 | 48 | void get_command_submenu(std::vector&); 49 | //int init_global(HANDLE&, PWSTR, int&); 50 | int init_global(HANDLE&, HICON&); 51 | 52 | void create_process(nlohmann::json& jsp, const HANDLE&, bool runas_admin = false, bool log_crontab = false); 53 | void show_hide_toggle(nlohmann::json& jsp); 54 | void open_path(nlohmann::json& jsp); 55 | void select_file(nlohmann::json& jsp); 56 | void disable_enable_menu(nlohmann::json& jsp, HANDLE, bool runas_admin = false); 57 | 58 | BOOL undock_window(int idx); 59 | BOOL hide_current_window(HWND hwnd); 60 | 61 | void hideshow_all(bool is_hideall = true); 62 | void start_all(HANDLE, bool force = false); 63 | void restart_all(HANDLE); 64 | void update_hwnd_all(); 65 | void unregisterhotkey_killtimer_all(); 66 | void kill_all(bool is_exit = true, int exclusion_id = 0); 67 | void left_click_toggle(); 68 | 69 | 70 | #define CLEAN_MUTEX() { \ 71 | LOGMESSAGE(L"CLEAN_MUTEX ghMutex:0x%x\n",ghMutex); \ 72 | if(ghMutex)ReleaseMutex(ghMutex); \ 73 | if(ghMutex)CloseHandle(ghMutex); \ 74 | ghMutex = NULL; \ 75 | } 76 | 77 | //#define CLEANUP_BEFORE_QUIT() {delete_lockfile();kill_all();DeleteTrayIcon();} 78 | #define CLEANUP_BEFORE_QUIT(where) {\ 79 | kill_all(); \ 80 | unregisterhotkey_killtimer_all(); \ 81 | undock_window(-1); \ 82 | CLEAN_MUTEX(); \ 83 | DeleteTrayIcon(); \ 84 | if (gHicon)DestroyIcon(gHicon); \ 85 | gHicon = NULL; \ 86 | /*if(enable_critialsection)DeleteCriticalSection(&CriticalSection);*/ \ 87 | LOGMESSAGE(L"CLEANUP_BEFORE_QUIT ghMutex:0x%x where:%d\n",ghMutex,where); \ 88 | } 89 | 90 | // UnregisterClass(szWindowClass, hInst); 91 | -------------------------------------------------------------------------------- /CommandTrayHost/language_data.h: -------------------------------------------------------------------------------- 1 | 2 | // https://msdn.microsoft.com/en-us/library/cc233982.aspx 3 | 4 | // language_tag is from above link. 5 | // You must use strict format of json format 6 | const nlohmann::json language_alias = u8R"json({ 7 | "zh-Hans": "zh-CN", 8 | "zh": "zh-CN", 9 | "zh-SG": "zh-CN" 10 | })json"_json; 11 | const nlohmann::json language_data = u8R"json({ 12 | "en-US": { 13 | "Hide All": "Hide All", 14 | "Disable All": "Disable All", 15 | "Enable All": "Enable All", 16 | "Show All": "Show All", 17 | "Restart All":"Restart All", 18 | "All": "All", 19 | "Start on Boot": "Start on Boot", 20 | "Elevate": "Elevate", 21 | "Show": "Show", 22 | "Hide": "Hide", 23 | "Reload": "Reload", 24 | "Home": "Home", 25 | "About": "About", 26 | "Help": "Help", 27 | "Exit": "Exit", 28 | "": "", 29 | "Set IE Proxy": "Set IE Proxy", 30 | "Daemon": "Daemon", 31 | "Enable": "Enable", 32 | "Disable": "Disable", 33 | "Restart Command": "Restart Command", 34 | "Run As Administrator": "Run As Administrator", 35 | "Could not AssignProcessToObject. If not show up, you maybe need to kill the process by TaskManager.": "Could not AssignProcessToObject. If not show up, you maybe need to kill the process by TaskManager.", 36 | "CommandTrayHost Started,Click Tray icon to Hide/Show Console.": "CommandTrayHost Started,Click Tray icon to Hide/Show Console.", 37 | "Clear cache?": "Clear cache?", 38 | "You just edit config.json!\n\nChoose Yes to clear cache\n\nChoose No to keep expired cache.":"You just edit config.json!\n\nChoose Yes to clear cache\n\nChoose No to keep expired cache.", 39 | "\n\nChoose Cancel to do nothing": "\n\nChoose Cancel to do nothing", 40 | "Check for Updates...": "Check for Updates...", 41 | "New version found! Download?\n\n": "New version found! Download?\n\n" 42 | } 43 | })json"_json; 44 | 45 | /************************************************************************/ 46 | /* Example: 47 | * 48 | * ,"test-CN": { 49 | "Hide All": "隐藏全部test", 50 | "Disable All": "全部禁用test", 51 | "Enable All": "全部启动test", 52 | "Show All": "全部显示test", 53 | "All": "全部test", 54 | "Start on Boot": "开机启动test", 55 | "Elevate": "提权test", 56 | "Show": "显示test", 57 | "Hide": "隐藏test", 58 | "Reload": "重启命令test", 59 | "Home": "主页test", 60 | "About": "关于test", 61 | "Help": "帮助test", 62 | "Exit": "退出test", 63 | "": "", 64 | "Set IE Proxy": "设置IE代理test", 65 | "Daemon": "应用test", 66 | "Enable": "启用test", 67 | "Disable": "停用test", 68 | "Restart Command": "重启命令test", 69 | "Run As Administrator": "管理员运行test" 70 | } */ 71 | /************************************************************************/ -------------------------------------------------------------------------------- /third_party/ccronexpr/ccronexpr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015, alex at staticlibs.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * File: ccronexpr.h 19 | * Author: alex 20 | * 21 | * Created on February 24, 2015, 9:35 AM 22 | */ 23 | 24 | #ifndef CCRONEXPR_H 25 | #define CCRONEXPR_H 26 | 27 | #if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) 28 | extern "C" { 29 | #endif 30 | 31 | #ifndef ANDROID 32 | #include 33 | #else /* ANDROID */ 34 | #include 35 | #endif /* ANDROID */ 36 | 37 | #include /*added for use if uint*_t data types*/ 38 | 39 | #ifndef ARRAY_LEN 40 | #define ARRAY_LEN(x) sizeof(x)/sizeof(x[0]) 41 | #endif 42 | 43 | #ifdef __MINGW32__ 44 | /* To avoid warning when building with mingw */ 45 | time_t _mkgmtime(struct tm* tm); 46 | #endif /* __MINGW32__ */ 47 | 48 | /** 49 | * Parsed cron expression 50 | */ 51 | typedef struct { 52 | uint8_t seconds[8]; 53 | uint8_t minutes[8]; 54 | uint8_t hours[3]; 55 | uint8_t days_of_week[1]; 56 | uint8_t days_of_month[4]; 57 | uint8_t months[2]; 58 | } cron_expr; 59 | 60 | /** 61 | * Parses specified cron expression. 62 | * 63 | * @param expression cron expression as nul-terminated string, 64 | * should be no longer that 256 bytes 65 | * @param target pointer to cron expression structure, it's client code responsibility 66 | * to free/destroy it afterwards 67 | * @param error output error message, will be set to string literal 68 | * error message in case of error. Will be set to NULL on success. 69 | * The error message should NOT be freed by client. 70 | */ 71 | void cron_parse_expr(const char* expression, cron_expr* target, const char** error); 72 | 73 | /** 74 | * Uses the specified expression to calculate the next 'fire' date after 75 | * the specified date. All dates are processed as UTC (GMT) dates 76 | * without timezones information. To use local dates (current system timezone) 77 | * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' 78 | * 79 | * @param expr parsed cron expression to use in next date calculation 80 | * @param date start date to start calculation from 81 | * @return next 'fire' date in case of success, '((time_t) -1)' in case of error. 82 | */ 83 | time_t cron_next(cron_expr* expr, time_t date); 84 | 85 | 86 | #if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) 87 | } /* extern "C"*/ 88 | #endif 89 | 90 | #endif /* CCRONEXPR_H */ 91 | 92 | -------------------------------------------------------------------------------- /third_party/ccronexpr/README.md: -------------------------------------------------------------------------------- 1 | Cron expression parsing in ANSI C 2 | ================================= 3 | 4 | Given a cron expression and a date, you can get the next date which satisfies the cron expression. 5 | 6 | Supports cron expressions with `seconds` field. Based on implementation of [CronSequenceGenerator](https://github.com/spring-projects/spring-framework/blob/babbf6e8710ab937cd05ece20270f51490299270/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java) from Spring Framework. 7 | 8 | Compiles and should work on Linux (GCC/Clang), Mac OS (Clang), Windows (MSVC), Android NDK, iOS and possibly on other platforms with `time.h` support. 9 | 10 | Supports compilation in C (89) and in C++ modes. 11 | 12 | Usage example 13 | ------------- 14 | 15 | #include "ccronexpr.h" 16 | 17 | cron_expr expr; 18 | const char* err = NULL; 19 | cron_parse_expr("0 */2 1-4 * * *", &expr, &err); 20 | if (err) ... /* invalid expression */ 21 | time_t cur = time(NULL); 22 | time_t next = cron_next(&expr, cur); 23 | 24 | 25 | Compilation and tests run examples 26 | ---------------------------------- 27 | 28 | gcc ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && ./a.out 29 | g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && ./a.out 30 | g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && ./a.out 31 | 32 | clang ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && ./a.out 33 | clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && ./a.out 34 | clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && ./a.out 35 | 36 | cl ccronexpr.c ccronexpr_test.c /W4 /D_CRT_SECURE_NO_WARNINGS && ccronexpr.exe 37 | 38 | Examples of supported expressions 39 | --------------------------------- 40 | 41 | Expression, input date, next date: 42 | 43 | "*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-02_01:00:00" 44 | "0 */2 1-4 * * *", "2012-07-01_09:00:00", "2012-07-02_01:00:00" 45 | "0 0 7 ? * MON-FRI", "2009-09-26_00:42:55", "2009-09-28_07:00:00" 46 | "0 30 23 30 1/3 ?", "2011-04-30_23:30:00", "2011-07-30_23:30:00" 47 | 48 | See more examples in tests. 49 | 50 | Timezones 51 | --------- 52 | 53 | This implementation does not support explicit timezones handling. By default all dates are 54 | processed as UTC (GMT) dates without timezone infomation. 55 | 56 | To use local dates (current system timezone) instead of GMT compile with `-DCRON_USE_LOCAL_TIME`. 57 | 58 | License information 59 | ------------------- 60 | 61 | This project is released under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) 62 | 63 | Changelog 64 | --------- 65 | 66 | **2017-09-24 67 | 68 | * merged #4 69 | 70 | **2016-06-17** 71 | 72 | * use thread-safe versions of `gmtime` and `localtime` 73 | 74 | **2015-02-28** 75 | 76 | * initial public version 77 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 2.3-b{build} 2 | # version: 0.6-latest 3 | skip_tags: true 4 | image: Visual Studio 2019 5 | configuration: Release 6 | platform: 7 | - x86 8 | # - x64 9 | init: 10 | - cmd: echo %cd% 11 | - cmd: curl -L -o %temp%\add_gitutf16.py https://raw.githubusercontent.com/rexdf/CommandTrayHost/master/add_gitutf16.py 12 | - cmd: C:\Python39\python.exe %temp%\add_gitutf16.py 13 | # clone_depth: 1 14 | clone_folder: c:\projects\CommandTrayHost 15 | install: 16 | - cmd: >- 17 | cd /d c:\tools\vcpkg 18 | 19 | rem git reset --hard 20 | 21 | rem git clean -ffdx 22 | 23 | rem git pull 24 | 25 | rem bootstrap-vcpkg.bat 26 | 27 | cd /d c:\projects\CommandTrayHost 28 | 29 | vcpkg install rapidjson rapidjson:x64-windows nlohmann-json nlohmann-json:x64-windows 30 | 31 | C:\Python39\python.exe vcpkg_patch.py 32 | before_build: 33 | # - cmd: cd c:\projects\CommandTrayHost 34 | - cmd: >- 35 | C:\Python39\Scripts\pip.exe install chardet pytz 36 | 37 | C:\Python39\python.exe version_set.py %APPVEYOR_BUILD_NUMBER% 2 3 4 38 | build: 39 | project: c:\projects\CommandTrayHost\CommandTrayHost.sln 40 | verbosity: minimal 41 | after_build: 42 | - cmd: msbuild "c:\projects\CommandTrayHost\CommandTrayHost.sln" /verbosity:minimal /p:Configuration=Release /p:Platform=x64 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" 43 | - cmd: msbuild "c:\projects\CommandTrayHost\CommandTrayHost.sln" /verbosity:minimal /p:Configuration=XP-Release /p:Platform=x86 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" 44 | # - cmd: cd c:\projects\CommandTrayHost 45 | - cmd: >- 46 | mkdir CommandTrayHost-x64 47 | 48 | mkdir CommandTrayHost-x86 49 | 50 | mkdir CommandTrayHost-xp-x86 51 | 52 | copy x64\Release\CommandTrayHost.exe CommandTrayHost-x64\ 53 | 54 | copy Release\CommandTrayHost.exe CommandTrayHost-x86\ 55 | 56 | copy XP-Release\CommandTrayHost.exe CommandTrayHost-xp-x86\ 57 | 58 | curl -L -o upx.zip https://github.com/upx/upx/releases/download/v3.96/upx-3.96-win64.zip 59 | 60 | 7z e upx.zip *.exe -r 61 | 62 | upx CommandTrayHost-x64\CommandTrayHost.exe 63 | 64 | upx CommandTrayHost-x86\CommandTrayHost.exe 65 | 66 | upx CommandTrayHost-xp-x86\CommandTrayHost.exe 67 | 68 | 7z a -tzip CommandTrayHost-%APPVEYOR_BUILD_VERSION%.zip .\CommandTrayHost-x64\ .\CommandTrayHost-x86\ .\CommandTrayHost-xp-x86\ 69 | artifacts: 70 | - path: CommandTrayHost-%APPVEYOR_BUILD_VERSION%.zip 71 | name: CommandTrayHost 72 | 73 | deploy: 74 | release: $(appveyor_build_version) 75 | description: "nightly" 76 | provider: GitHub 77 | auth_token: 78 | secure: 6BqGKqbQbDfnZ3Y7SHvdQkPVCjXcJ1GIgPe41oD8K7ttPY/ivWN3iMyq2eZ1s4RD # your encrypted token from GitHub 79 | artifact: CommandTrayHost # upload all NuGet packages to release assets 80 | prerelease: true 81 | force_update: true 82 | on: 83 | branch: release 84 | appveyor_repo_tag: false # deploy on tag push only 85 | 86 | cache: 87 | - c:\tools\vcpkg\installed\ -> vcpkg_cache.txt 88 | -------------------------------------------------------------------------------- /CommandTrayHost/test.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef _DEBUG 3 | void experimental_test() 4 | { 5 | cron_expr expr; 6 | const char* err = NULL; 7 | const int test_times = 1; // 50000; 8 | { 9 | auto begin = std::chrono::high_resolution_clock::now(); 10 | for (int i = 0; i < test_times; i++) { 11 | ZeroMemory(&expr, sizeof(expr)); 12 | cron_parse_expr("8 */2 15-16 29 2 *", &expr, &err); 13 | } 14 | auto end = std::chrono::high_resolution_clock::now(); 15 | LOGMESSAGE(L"cron_parse_expr %d times, cost %lld miliseconds\n", test_times, std::chrono::duration_cast(end - begin).count()); 16 | } 17 | if (err)LOGMESSAGE(L"cron_parse_expr err: %S\n", err); 18 | else LOGMESSAGE(L"cron_parse_expr ok!\n"); 19 | assert(0 == err); 20 | time_t cur = time(NULL); 21 | time_t next = cron_next(&expr, cur); 22 | 23 | LOGMESSAGE(L"%lld -> %lld diff:%lld", next, cur, next - cur); 24 | double dif = difftime(next, cur); 25 | char buffer[80]; 26 | tm t1, t2; 27 | localtime_s(&t1, &cur); 28 | localtime_s(&t2, &next); 29 | strftime(buffer, ARRAYSIZE(buffer), "%Y-%m-%d_%H:%M:%S", &t1); 30 | LOGMESSAGE(L"t1:%S %f\n", buffer, dif); 31 | strftime(buffer, ARRAYSIZE(buffer), "%Y-%m-%d_%H:%M:%S", &t2); 32 | LOGMESSAGE(L"t2:%S %f\n", buffer, dif); 33 | LOGMESSAGE(L"%llu %llu\n", ((time_t)-1), static_cast(-1)); 34 | LOGMESSAGE(L"ARRAYSIZE %d\n", ARRAYSIZE("start") - 1); 35 | LOGMESSAGE(L"%d\n", sizeof(cron_expr)); 36 | { 37 | LOGMESSAGE(L"cron_expr_begin"); 38 | for (int _i = 0; _i < 26; _i++) 39 | { 40 | LOGMESSAGE(L"%d", (reinterpret_cast(&expr))[_i]); 41 | } 42 | LOGMESSAGE(L"cron_expr_end\n"); 43 | uint8_t* pointer_st = reinterpret_cast(&expr); 44 | std::vector vector_instance = std::vector(pointer_st, pointer_st + 26); 45 | nlohmann::json j = vector_instance; 46 | LOGMESSAGE(L"0x%x 0x%x 0x%x 0x%x\n", 47 | pointer_st, 48 | vector_instance.data(), 49 | (j.get>().data()), 50 | (j.get>().data()) 51 | ); 52 | { 53 | auto begin = std::chrono::high_resolution_clock::now(); 54 | for (int i = 0; i < test_times; i++) 55 | { 56 | std::vector a = j.get>(); 57 | a.data(); 58 | } 59 | auto end = std::chrono::high_resolution_clock::now(); 60 | LOGMESSAGE(L"j.get>().data() %d times, cost %lld miliseconds\n", test_times, std::chrono::duration_cast(end - begin).count()); 61 | } 62 | } 63 | int kill_timeout = 5000; 64 | LOGMESSAGE(L"kill_timeout < static_cast(-2):%S %u\n", kill_timeout < static_cast(-2) ? "true" : "false", static_cast(-2)); 65 | if (0) { 66 | HRESULT hr; 67 | //LPCTSTR Url = _T("https://api.github.com/repos/rexdf/CommandTrayHost/releases/latest"), File = _T("latest_commandtrayhost.json"); 68 | LPCTSTR Url = L"http://127.0.0.1:5000", File = L"test.txt"; 69 | hr = URLDownloadToFile(0, Url, File, 0, 0); 70 | switch (hr) 71 | { 72 | case S_OK: 73 | LOGMESSAGE(L"Successful download\n"); 74 | break; 75 | case E_OUTOFMEMORY: 76 | LOGMESSAGE(L"Out of memory error\n"); 77 | break; 78 | case INET_E_DOWNLOAD_FAILURE: 79 | LOGMESSAGE(L"Cannot access server data\n"); 80 | break; 81 | default: 82 | LOGMESSAGE(L"Unknown error\n"); 83 | break; 84 | } 85 | } 86 | } 87 | #endif -------------------------------------------------------------------------------- /CommandTrayHost/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include "targetver.h" 9 | 10 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 11 | // Windows Header Files: 12 | #include 13 | 14 | // C RunTime Header Files 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | // TODO: reference additional headers your program requires here 31 | #include 32 | //#include 33 | #include // CComPtr 34 | #include 35 | 36 | #include 37 | 38 | #ifdef _DEBUG_PROCESS_TREE 39 | #include 40 | #endif 41 | 42 | #include 43 | #include 44 | //#include 45 | #include 46 | 47 | 48 | #include 49 | 50 | #ifdef _M_AMD64 51 | #define RAPIDJSON_48BITPOINTER_OPTIMIZATION 1 52 | #endif 53 | 54 | //#pragma warning( disable : 4003) 55 | #include 56 | #include 57 | //#include 58 | //#include 59 | //#include 60 | #include 61 | #include 62 | #include // FileReadStream 63 | #include // AutoUTFInputStream 64 | //#include 65 | 66 | #include 67 | #if VER_PRODUCTBUILD == 7600 68 | #include 69 | #endif 70 | 71 | //#if VER_PRODUCTBUILD == 7600 72 | //#pragma warning(disable : 4995) 73 | //#endif 74 | #include 75 | 76 | 77 | 78 | #define CRON_USE_LOCAL_TIME 79 | #define CRON_COMPILE_AS_CXX 80 | #include "ccronexpr.h" 81 | 82 | #ifdef _DEBUG 83 | //#define LOGMESSAGE( str ) OutputDebugString( str ); 84 | void log_message(PCSTR, PCSTR, int, PCWSTR, ...); 85 | //void log_message(PCSTR, PCSTR, int, PCSTR, ...); 86 | #define LOGMESSAGE( str, ... ) log_message(__FILE__,__FUNCTION__,__LINE__,str L"\n",__VA_ARGS__) 87 | #else 88 | #define LOGMESSAGE( str, ... ) 89 | #endif 90 | 91 | #define CLEANUP_HISTORY_STARTUP 92 | 93 | #ifdef CLEANUP_HISTORY_STARTUP 94 | #define CommandTrayHost (L"Command_Tray_Host") 95 | //#define LOCK_FILE_NAME L"commandtrayhost_lock_pid.txt" 96 | #endif 97 | 98 | #define CACHE_FILENAMEW L"command_tray_host.cache" 99 | #define CONFIG_FILENAMEW L"config.json" 100 | #define CACHE_FILENAMEA "command_tray_host.cache" 101 | #define CONFIG_FILENAMEA "config.json" 102 | 103 | #define VERSION_NUMS L"2.0.0" 104 | 105 | #define MAX_MENU_LEVEL_LIMIT 40 106 | 107 | #define BUILD_TIME_CN __TIMESTAMP__ 108 | #define BUILD_TIME_EN __TIMESTAMP__ 109 | 110 | #define UPDATE_TEMP_DIR L"temp" 111 | #if VER_PRODUCTBUILD == 7600 112 | #define UPDATE_URL L"http://api.rexdf.org/github/repos/rexdf/CommandTrayHost/releases" 113 | #else 114 | #define UPDATE_URL L"https://api.github.com/repos/rexdf/CommandTrayHost/releases" 115 | #endif 116 | 117 | -------------------------------------------------------------------------------- /CommandTrayHost/filewatcher.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "filewatcher.h" 3 | //#include "cache.h" 4 | #include "CommandTrayHost.h" 5 | 6 | //void WatchDirectory(LPTSTR lpDir) 7 | DWORD WINAPI WatchDirectory(LPVOID lpParam) 8 | { 9 | DWORD dwWaitStatus; 10 | HANDLE dwChangeHandles; 11 | 12 | // Watch the directory for file creation and deletion. 13 | 14 | dwChangeHandles = FindFirstChangeNotification( 15 | reinterpret_cast(lpParam), // directory to watch 16 | FALSE, // do not watch subtree 17 | FILE_NOTIFY_CHANGE_LAST_WRITE); // watch file name changes 18 | 19 | if (dwChangeHandles == INVALID_HANDLE_VALUE) 20 | { 21 | //printf("\n ERROR: FindFirstChangeNotification function failed.\n"); 22 | //ExitProcess(GetLastError()); 23 | return GetLastError(); 24 | } 25 | 26 | // Make a final validation check on our handles. 27 | 28 | if (dwChangeHandles == NULL) 29 | { 30 | //printf("\n ERROR: Unexpected NULL from FindFirstChangeNotification.\n"); 31 | //ExitProcess(GetLastError()); 32 | return GetLastError(); 33 | } 34 | 35 | // Change notification is set. Now wait on both notification 36 | // handles and refresh accordingly. 37 | 38 | while (TRUE) 39 | { 40 | // Wait for notification. 41 | 42 | //printf("\nWaiting for notification...\n"); 43 | 44 | dwWaitStatus = WaitForSingleObject(dwChangeHandles, INFINITE); 45 | 46 | switch (dwWaitStatus) 47 | { 48 | case WAIT_OBJECT_0: 49 | 50 | // A file was created, renamed, or deleted in the directory. 51 | // Refresh this directory and restart the notification. 52 | 53 | LOGMESSAGE(L"%s\n", reinterpret_cast(lpParam)); 54 | //is_cache_not_expired(true, true); 55 | Sleep(200); // wait a little while for windows finish writing files to disk. Otherwise, there will be multiple timestamp 56 | extern HWND hWnd; 57 | SendMessage(hWnd, WM_COMMAND, WM_TASKBARNOTIFY_MENUITEM_CHECK_CACHEVALID, NULL); 58 | Sleep(300); 59 | //PostMessage(hWnd, WM_COMMAND, WM_TASKBARNOTIFY_MENUITEM_CHECK_CACHEVALID, NULL); 60 | if (FindNextChangeNotification(dwChangeHandles) == FALSE) 61 | { 62 | return GetLastError(); 63 | //printf("\n ERROR: FindNextChangeNotification function failed.\n"); 64 | //ExitProcess(GetLastError()); 65 | } 66 | break; 67 | 68 | case WAIT_TIMEOUT: 69 | 70 | // A timeout occurred, this would happen if some value other 71 | // than INFINITE is used in the Wait call and no changes occur. 72 | // In a single-threaded environment you might not want an 73 | // INFINITE wait. 74 | 75 | //printf("\nNo changes in the timeout period.\n"); 76 | break; 77 | 78 | default: 79 | //printf("\n ERROR: Unhandled dwWaitStatus.\n"); 80 | //ExitProcess(GetLastError()); 81 | return GetLastError(); 82 | break; 83 | } 84 | } 85 | } 86 | 87 | BOOL CreateFileWatch(PWSTR lpDir, HANDLE &hThread) 88 | { 89 | hThread = CreateThread(NULL, // default security attributes 90 | 0, // use default stack size 91 | (LPTHREAD_START_ROUTINE)WatchDirectory, // thread function 92 | reinterpret_cast(lpDir), // no thread function argument 93 | 0, // use default creation flags 94 | NULL); 95 | if (hThread == NULL) 96 | { 97 | return FALSE; 98 | } 99 | return TRUE; 100 | } 101 | -------------------------------------------------------------------------------- /CommandTrayHost/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | std::string truncate(std::string str, size_t width, bool show_ellipsis = true); 4 | std::wstring utf8_to_wstring(const std::string&); 5 | std::string wstring_to_utf8(const std::wstring&); 6 | 7 | std::wstring get_abs_path(const std::wstring& path_wstring, const std::wstring& cmd_wstring); 8 | std::wstring get_abs_working_directory(const std::wstring& path_wstring, const std::wstring& working_directory_wstring); 9 | 10 | bool printf_to_bufferA(char* dst, size_t max_len, size_t& cursor, PCSTR fmt, ...); 11 | 12 | int64_t FileSize(PCWSTR); 13 | 14 | bool json_object_has_member(const nlohmann::json&, PCSTR); 15 | 16 | /*#ifdef _DEBUG 17 | #define try_read_optional_json(a,b,c) try_read_optional_json_debug(a,b,c,__FUNCTION__) 18 | #endif*/ 19 | 20 | /* 21 | * Make sure out is initialized with default value before call try_read_optional_json 22 | */ 23 | /*template 24 | #ifdef _DEBUG 25 | bool try_read_optional_json_debug(const nlohmann::json& root, Type& out, PCSTR query_string, PCSTR caller_fuc_name) 26 | #else 27 | bool try_read_optional_json(const nlohmann::json& root, Type& out, PCSTR query_string) 28 | #endif 29 | { 30 | //Type ignore_all = false; // Do it before call try_read_optional_json 31 | try 32 | { 33 | out = root.at(query_string); 34 | } 35 | #ifdef _DEBUG 36 | catch (nlohmann::json::out_of_range& e) 37 | #else 38 | catch (nlohmann::json::out_of_range&) 39 | #endif 40 | { 41 | LOGMESSAGE(L"%S %S out_of_range %S\n", caller_fuc_name, query_string, e.what()); 42 | return false; 43 | } 44 | catch (...) 45 | { 46 | msg_prompt(//NULL, 47 | (utf8_to_wstring(query_string) + L" type check failed!").c_str(), 48 | L"Type Error", 49 | MB_OK | MB_ICONERROR 50 | ); 51 | return false; 52 | } 53 | return true; 54 | } 55 | */ 56 | 57 | //void rapidjson_merge_object(rapidjson::Value &dstObject, rapidjson::Value &srcObject, rapidjson::Document::AllocatorType &allocator); 58 | 59 | 60 | //bool operator != (const RECT&, const RECT&); 61 | 62 | BOOL get_alpha(HWND hwnd, BYTE& alpha, bool no_exstyle_return = false); 63 | 64 | HWND GetHwnd(HANDLE hProcess, size_t& num_of_windows, int idx = 0); 65 | 66 | int get_caption_from_hwnd(HWND hwnd, std::wstring& caption); 67 | 68 | BOOL is_hwnd_valid_caption(HWND hwnd, PCWSTR caption_, DWORD pid_); 69 | 70 | #ifdef _DEBUG 71 | void check_and_kill(HANDLE hProcess, DWORD pid, DWORD timeout, PCWSTR name,bool kill_process_tree, bool is_update_cache = true); 72 | #else 73 | void check_and_kill(HANDLE hProcess, DWORD pid, DWORD timeout,bool kill_process_tree, bool is_update_cache = true); 74 | #endif 75 | 76 | int msg_prompt( 77 | _In_opt_ LPCTSTR lpText, 78 | _In_opt_ LPCTSTR lpCaption, 79 | _In_ UINT uType = MB_OK 80 | ); 81 | 82 | BOOL get_hicon(PCWSTR, int, HICON&, bool share = false); 83 | 84 | #ifdef _DEBUG 85 | inline BOOL get_wnd_rect(HWND hWnd, RECT& rect) 86 | { 87 | return GetWindowRect(hWnd, &rect); 88 | } 89 | #endif 90 | 91 | BOOL set_wnd_pos( 92 | HWND hWnd, 93 | int x, int y, int cx, int cy 94 | , bool top_most 95 | , bool use_pos 96 | , bool use_size 97 | //, bool is_show 98 | ); 99 | 100 | BOOL set_wnd_alpha(HWND hWnd, BYTE bAlpha); 101 | 102 | BOOL set_wnd_icon(HWND hWnd, HICON hIcon); 103 | 104 | BOOL GetStockIcon(HICON& outHicon); 105 | 106 | bool registry_hotkey(const char* s, int id, PCWSTR msg, bool show_error = true); 107 | 108 | #ifdef _DEBUG 109 | void ChangeIcon(const HICON); 110 | #endif 111 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | *.c text 7 | *.cc text 8 | *.cxx text 9 | *.cpp text 10 | *.c++ text 11 | *.hpp text 12 | *.h text 13 | *.h++ text 14 | *.hh text 15 | 16 | *.sln text eol=crlf 17 | *.csproj text eol=crlf 18 | *.vbproj text eol=crlf 19 | *.vcxproj text eol=crlf 20 | *.vcproj text eol=crlf 21 | *.dbproj text eol=crlf 22 | *.fsproj text eol=crlf 23 | *.lsproj text eol=crlf 24 | *.wixproj text eol=crlf 25 | *.modelproj text eol=crlf 26 | *.sqlproj text eol=crlf 27 | *.wmaproj text eol=crlf 28 | 29 | *.xproj text eol=crlf 30 | *.props text eol=crlf 31 | *.filters text eol=crlf 32 | *.vcxitems text eol=crlf 33 | 34 | #*.h filter=utf16 diff 35 | #*.cpp filter=utf16 diff 36 | #*.rc filter=utf16 diff 37 | #CommandTrayHost\resource.h filter=utf16 diff 38 | #CommandTrayHost\CommandTrayHost.rc filter=utf16 diff 39 | 40 | # CommandTrayHost/resource.h merge=binary 41 | # *.rc merge=binary 42 | 43 | *.rc filter=utf16 44 | resource.h filter=utf16 45 | 46 | ############################################################################### 47 | # Set default behavior for command prompt diff. 48 | # 49 | # This is need for earlier builds of msysgit that does not have it on by 50 | # default for csharp files. 51 | # Note: This is only used by command line 52 | ############################################################################### 53 | #*.cs diff=csharp 54 | 55 | ############################################################################### 56 | # Set the merge driver for project and solution files 57 | # 58 | # Merging from the command prompt will add diff markers to the files if there 59 | # are conflicts (Merging from VS is not affected by the settings below, in VS 60 | # the diff markers are never inserted). Diff markers may cause the following 61 | # file extensions to fail to load in VS. An alternative would be to treat 62 | # these files as binary and thus will always conflict and require user 63 | # intervention with every merge. To do so, just uncomment the entries below 64 | ############################################################################### 65 | #*.sln merge=binary 66 | #*.csproj merge=binary 67 | #*.vbproj merge=binary 68 | #*.vcxproj merge=binary 69 | #*.vcproj merge=binary 70 | #*.dbproj merge=binary 71 | #*.fsproj merge=binary 72 | #*.lsproj merge=binary 73 | #*.wixproj merge=binary 74 | #*.modelproj merge=binary 75 | #*.sqlproj merge=binary 76 | #*.wwaproj merge=binary 77 | 78 | ############################################################################### 79 | # behavior for image files 80 | # 81 | # image files are treated as binary by default. 82 | ############################################################################### 83 | #*.jpg binary 84 | #*.png binary 85 | #*.gif binary 86 | 87 | ############################################################################### 88 | # diff behavior for common document formats 89 | # 90 | # Convert binary document formats to text before diffing them. This feature 91 | # is only available from the command line. Turn it on by uncommenting the 92 | # entries below. 93 | ############################################################################### 94 | #*.doc diff=astextplain 95 | #*.DOC diff=astextplain 96 | #*.docx diff=astextplain 97 | #*.DOCX diff=astextplain 98 | #*.dot diff=astextplain 99 | #*.DOT diff=astextplain 100 | #*.pdf diff=astextplain 101 | #*.PDF diff=astextplain 102 | #*.rtf diff=astextplain 103 | #*.RTF diff=astextplain 104 | -------------------------------------------------------------------------------- /CommandTrayHost/CommandTrayHost.rc: -------------------------------------------------------------------------------- 1 | //Microsoft Developer Studio generated resource script. 2 | // 3 | #include "resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Generated from the TEXTINCLUDE 2 resource. 9 | // 10 | #define APSTUDIO_HIDDEN_SYMBOLS 11 | #include "windows.h" 12 | #undef APSTUDIO_HIDDEN_SYMBOLS 13 | #include "resource.h" 14 | 15 | ///////////////////////////////////////////////////////////////////////////// 16 | #undef APSTUDIO_READONLY_SYMBOLS 17 | 18 | ///////////////////////////////////////////////////////////////////////////// 19 | // Neutral resources 20 | 21 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU) 22 | #ifdef _WIN32 23 | LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL 24 | //#pragma code_page(936) 25 | #endif //_WIN32 26 | 27 | #ifndef _MAC 28 | ///////////////////////////////////////////////////////////////////////////// 29 | // 30 | // Version 31 | // 32 | 33 | VS_VERSION_INFO VERSIONINFO 34 | FILEVERSION 1, 0, 0, 0 35 | PRODUCTVERSION 1, 0, 0, 0 36 | FILEFLAGSMASK 0x3fL 37 | #ifdef _DEBUG 38 | FILEFLAGS 0x1L 39 | #else 40 | FILEFLAGS 0x0L 41 | #endif 42 | FILEOS 0x40004L 43 | FILETYPE 0x1L 44 | FILESUBTYPE 0x0L 45 | BEGIN 46 | BLOCK "StringFileInfo" 47 | BEGIN 48 | BLOCK "000004b0" 49 | BEGIN 50 | VALUE "Comments", "\0" 51 | VALUE "CompanyName", "rexdf@vip.qq.com\0" 52 | VALUE "FileDescription", "CommandTrayHost(taskbar)\0" 53 | VALUE "FileVersion", "1, 0, 0, 0\0" 54 | VALUE "InternalName", "CommandTrayHost\0" 55 | VALUE "LegalCopyright", "MIT License\0" 56 | VALUE "LegalTrademarks", "\0" 57 | VALUE "OriginalFilename", "CommandTrayHost.exe\0" 58 | VALUE "PrivateBuild", "\0" 59 | VALUE "ProductName", "CommandTrayHost taskbar\0" 60 | VALUE "ProductVersion", "2, 0, 0, 0\0" 61 | VALUE "SpecialBuild", "\0" 62 | END 63 | END 64 | BLOCK "VarFileInfo" 65 | BEGIN 66 | VALUE "Translation", 0x0, 1200 67 | END 68 | END 69 | 70 | #endif // !_MAC 71 | 72 | #endif // Neutral resources 73 | ///////////////////////////////////////////////////////////////////////////// 74 | 75 | 76 | ///////////////////////////////////////////////////////////////////////////// 77 | // Chinese (P.R.C.) resources 78 | 79 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) 80 | #ifdef _WIN32 81 | LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED 82 | #pragma code_page(936) 83 | #endif //_WIN32 84 | 85 | ///////////////////////////////////////////////////////////////////////////// 86 | // 87 | // Icon 88 | // 89 | 90 | // Icon with lowest ID value placed first to ensure application icon 91 | // remains consistent on all systems. 92 | IDI_TASKBAR ICON DISCARDABLE "taskbar.ico" 93 | IDI_SMALL ICON DISCARDABLE "small.ico" 94 | 95 | #ifdef APSTUDIO_INVOKED 96 | ///////////////////////////////////////////////////////////////////////////// 97 | // 98 | // TEXTINCLUDE 99 | // 100 | 101 | 2 TEXTINCLUDE DISCARDABLE 102 | BEGIN 103 | "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" 104 | "#include ""windows.h""\r\n" 105 | "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" 106 | "#include ""resource.h""\r\n" 107 | "\0" 108 | END 109 | 110 | 3 TEXTINCLUDE DISCARDABLE 111 | BEGIN 112 | "\r\n" 113 | "\0" 114 | END 115 | 116 | 1 TEXTINCLUDE DISCARDABLE 117 | BEGIN 118 | "resource.h\0" 119 | END 120 | 121 | #endif // APSTUDIO_INVOKED 122 | 123 | 124 | ///////////////////////////////////////////////////////////////////////////// 125 | // 126 | // String Table 127 | // 128 | 129 | STRINGTABLE DISCARDABLE 130 | BEGIN 131 | IDS_CMDLINE L"cmd /v" 132 | IDS_ENVIRONMENT L"TASKBAR_VISIBLE=0\nTASKBAR_TOOLTIP=CommandTrayHost\nTASKBAR_TITLE=CommandTrayHost Notify\nTASKBAR_BALLOON=CommandTrayHost 已经启动,可自定义单击托盘行为、右击显示菜单内容。\n" 133 | #ifdef _DEBUG2 134 | IDS_PROXYLIST L"http://127.0.0.1:8087/proxy.pac\n127.0.0.1:8087\n" 135 | IDS_RASPBK "%APPDATA%\\Microsoft\\Network\\Connections\\Pbk\n%ALLUSERSPROFILE%\\Microsoft\\Network\\Connections\\Pbk\n%ALLUSERSPROFILE%\\Application Data\\Microsoft\\Network\\Connections\\Pbk\\rasphone.pbk\n" 136 | #endif 137 | END 138 | 139 | #endif // Chinese (P.R.C.) resources 140 | ///////////////////////////////////////////////////////////////////////////// 141 | 142 | 143 | 144 | #ifndef APSTUDIO_INVOKED 145 | ///////////////////////////////////////////////////////////////////////////// 146 | // 147 | // Generated from the TEXTINCLUDE 3 resource. 148 | // 149 | 150 | 151 | ///////////////////////////////////////////////////////////////////////////// 152 | #endif // not APSTUDIO_INVOKED 153 | 154 | -------------------------------------------------------------------------------- /CommandTrayHost/CommandTrayHost.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files 47 | 48 | 49 | Header Files 50 | 51 | 52 | Header Files 53 | 54 | 55 | Header Files 56 | 57 | 58 | Header Files 59 | 60 | 61 | Header Files 62 | 63 | 64 | Header Files 65 | 66 | 67 | Header Files 68 | 69 | 70 | 71 | 72 | Source Files 73 | 74 | 75 | Source Files 76 | 77 | 78 | Source Files 79 | 80 | 81 | Source Files 82 | 83 | 84 | Source Files 85 | 86 | 87 | Source Files 88 | 89 | 90 | Source Files 91 | 92 | 93 | Source Files 94 | 95 | 96 | Source Files 97 | 98 | 99 | Source Files 100 | 101 | 102 | Source Files 103 | 104 | 105 | Source Files 106 | 107 | 108 | 109 | 110 | Resource Files 111 | 112 | 113 | 114 | 115 | Resource Files 116 | 117 | 118 | Resource Files 119 | 120 | 121 | Resource Files 122 | 123 | 124 | -------------------------------------------------------------------------------- /CommandTrayHost/cache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //bool is_cache_not_expired(bool is_from_flush = false/*, bool is_from_other_thread = false*/); 4 | int is_cache_not_expired2(); 5 | 6 | bool is_config_changed(); 7 | bool reload_config(); 8 | bool get_filetime(PCWSTR const filename, FILETIME& file_write_timestamp); 9 | 10 | bool flush_cache(/*bool is_exist = false*/); 11 | 12 | enum CacheType 13 | { 14 | cPosition = 0, 15 | cSize = 1, 16 | cEnabled = 2, 17 | cShow = 3, 18 | cAlpha = 4, 19 | }; 20 | 21 | /* 22 | * before call update_cache() 23 | * we need to check enable_cache and 24 | * disable_cache_position etc 25 | * make sure cache_config_cursor correct 26 | */ 27 | template 28 | void update_cache(/*int index, */PCSTR name, T value, const CacheType cnt) 29 | { 30 | assert(enable_cache); 31 | //static auto& base_cache_ref = (*global_cache_configs_pointer); 32 | //static auto& base_main_ref = (*global_configs_pointer); 33 | //static auto& base_cache_ref = global_stat["cache"]["configs"]; 34 | //static auto& base_main_ref = global_stat["configs"]; 35 | extern int cache_config_cursor; 36 | extern nlohmann::json* global_cache_configs_pointer; 37 | //extern nlohmann::json* global_configs_pointer; 38 | auto& cache_ref = (*global_cache_configs_pointer)[cache_config_cursor][name]; 39 | //auto& main_ref= base_main_ref[cache_config_cursor][name]; 40 | if (cache_ref != value) 41 | { 42 | cache_ref = value; 43 | is_cache_valid = false; 44 | // why we need it? when restart app 45 | //auto& main_config_i_ref = (*global_configs_pointer)[cache_config_cursor]; 46 | /*if (0 == strcmp(name, "start_show")) 47 | { 48 | main_config_i_ref[name] = value; 49 | } 50 | else if (0 == strcmp(name, "alpha") && json_object_has_member(main_config_i_ref, "alpha")) 51 | { 52 | main_config_i_ref[name] = value; 53 | }*/ 54 | //base_ref[cache_config_cursor]["valid"] |= (1 << cnt); 55 | //LOGMESSAGE(L"base_ref[cache_config_cursor]["valid"]: 0x%x\n", base_ref[cache_config_cursor]["valid"].get()); 56 | 57 | auto& valid_ref = (*global_cache_configs_pointer)[cache_config_cursor]["valid"]; 58 | const int valid_value = valid_ref, valid_mask = 1 << cnt; 59 | if ((valid_value & valid_mask) == 0) 60 | { 61 | valid_ref = valid_value | valid_mask; 62 | } 63 | LOGMESSAGE(L"cache updated! %S\n", name); 64 | //flush_cache(); 65 | } 66 | } 67 | 68 | void update_cache_enabled_start_show(bool enabled, bool start_show); 69 | void update_cache_position_size(HWND hWnd); 70 | 71 | inline bool check_cache_valid(const int valid, const CacheType cnt) 72 | { 73 | assert(cnt >= 0 && cnt < 5); 74 | return valid & (1 << cnt); 75 | } 76 | 77 | template 78 | T get_cache(PCSTR name) 79 | { 80 | assert(enable_cache); 81 | //static auto& base_ref = global["cache"]["configs"]; 82 | extern int cache_config_cursor; 83 | extern nlohmann::json* global_cache_configs_pointer; 84 | return (*global_cache_configs_pointer)[cache_config_cursor][name]; 85 | } 86 | 87 | /* 88 | * type: -1 means cache_name invalid, return idx 89 | * 0 fatal error 90 | * 1 return from cache memory 91 | * 2 return from cache file 92 | * 3 return from config.json 93 | * 4 not found from above all, return idx 94 | */ 95 | rapidjson::SizeType get_cache_index( 96 | const rapidjson::Value& d_configs, // d["configs"] 97 | const rapidjson::Value* d_cache_configs, // &(d_cache["configs"]) 98 | const PCSTR cache_name, 99 | //PCSTR cache_item_name, 100 | const rapidjson::SizeType cache_size, // d_cache["configs"].Size() 101 | const rapidjson::SizeType global_cache_size, // global_stat["cache"]["configs"].size() 102 | const rapidjson::SizeType idx, 103 | int& type 104 | ); 105 | 106 | template 107 | Type get_cache_value( 108 | const rapidjson::Value* d_cache_configs, 109 | const rapidjson::SizeType cache_size, 110 | const rapidjson::SizeType idx, 111 | const size_t global_cache_size, 112 | PCSTR name, 113 | const int find_type, 114 | Type default_val 115 | ) 116 | { 117 | //if (global_stat != nullptr && global_cache_configs_pointer != nullptr) 118 | { 119 | if (find_type == 1 && idx < global_cache_size) 120 | { 121 | assert(global_stat != nullptr && global_cache_configs_pointer != nullptr); 122 | auto& global_cache_config_i_ref = (*global_cache_configs_pointer)[idx]; 123 | /*//#ifdef _DEBUG 124 | // try_read_optional_json(global_cache_config_i_ref, default_val, name, __FUNCTION__); 125 | //#else 126 | try_read_optional_json(global_cache_config_i_ref, default_val, name); 127 | //#endif 128 | return default_val;*/ 129 | return global_cache_config_i_ref.value(name, default_val); 130 | } 131 | } 132 | 133 | if (find_type == 2 && idx < cache_size) 134 | { 135 | auto& d_cache_config_i_ref = (*d_cache_configs)[idx]; 136 | if (d_cache_config_i_ref.HasMember(name)) 137 | { 138 | return d_cache_config_i_ref[name].Get(); 139 | } 140 | } 141 | 142 | return default_val; 143 | } 144 | 145 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | XP-Release/ 19 | x64/ 20 | x86/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | [Ll]og/ 25 | 26 | # Visual Studio 2015 cache/options directory 27 | .vs/ 28 | # Uncomment if you have tasks that create the project's static files in wwwroot 29 | #wwwroot/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # DNX 45 | project.lock.json 46 | project.fragment.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | .builds 70 | *.pidb 71 | *.svclog 72 | *.scc 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opendb 82 | *.opensdf 83 | *.sdf 84 | *.cachefile 85 | *.VC.db 86 | *.VC.VC.opendb 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | *.sap 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding add-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # NCrunch 115 | _NCrunch_* 116 | .*crunch*.local.xml 117 | nCrunchTemp_* 118 | 119 | # MightyMoose 120 | *.mm.* 121 | AutoTest.Net/ 122 | 123 | # Web workbench (sass) 124 | .sass-cache/ 125 | 126 | # Installshield output folder 127 | [Ee]xpress/ 128 | 129 | # DocProject is a documentation generator add-in 130 | DocProject/buildhelp/ 131 | DocProject/Help/*.HxT 132 | DocProject/Help/*.HxC 133 | DocProject/Help/*.hhc 134 | DocProject/Help/*.hhk 135 | DocProject/Help/*.hhp 136 | DocProject/Help/Html2 137 | DocProject/Help/html 138 | 139 | # Click-Once directory 140 | publish/ 141 | 142 | # Publish Web Output 143 | *.[Pp]ublish.xml 144 | *.azurePubxml 145 | # TODO: Comment the next line if you want to checkin your web deploy settings 146 | # but database connection strings (with potential passwords) will be unencrypted 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 151 | # checkin your Azure Web App publish settings, but sensitive information contained 152 | # in these scripts will be unencrypted 153 | PublishScripts/ 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Windows Store app package directories and files 176 | AppPackages/ 177 | BundleArtifacts/ 178 | Package.StoreAssociation.xml 179 | _pkginfo.txt 180 | 181 | # Visual Studio cache files 182 | # files ending in .cache can be ignored 183 | *.[Cc]ache 184 | # but keep track of directories ending in .cache 185 | !*.[Cc]ache/ 186 | 187 | # Others 188 | ClientBin/ 189 | ~$* 190 | *~ 191 | *.dbmdl 192 | *.dbproj.schemaview 193 | *.jfm 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # CodeRush 258 | .cr/ 259 | 260 | # Python Tools for Visual Studio (PTVS) 261 | __pycache__/ 262 | *.pyc 263 | 264 | # Visual Studio Code 265 | .vscode/ 266 | 267 | enc_temp_folder/ 268 | 269 | -------------------------------------------------------------------------------- /version_set.py: -------------------------------------------------------------------------------- 1 | # /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import re 6 | import sys 7 | import codecs 8 | import traceback 9 | import chardet 10 | 11 | MAJOR_VERSION = '0' 12 | MINOR_VERSION = '6' 13 | FIX_VERSION = '0' 14 | 15 | ''' 16 | git checkout HEAD -- CommandTrayHost/CommandTrayHost.rc CommandTrayHost/stdafx.h 17 | ''' 18 | 19 | 20 | def local_time(zone='Asia/Shanghai'): 21 | from datetime import datetime 22 | from pytz import timezone 23 | other_zone = timezone(zone) 24 | other_zone_time = datetime.now(other_zone) 25 | return other_zone_time.strftime('%Y-%m-%d %H:%M:%S %Z%z') 26 | 27 | 28 | def main(): 29 | global MAJOR_VERSION, MINOR_VERSION, FIX_VERSION 30 | print(sys.argv) 31 | if len(sys.argv) < 2: 32 | print("must input build version") 33 | return False 34 | argc = len(sys.argv) 35 | if(argc > 1): 36 | build_version_number = sys.argv[1] 37 | print("build_version_number: ", build_version_number) 38 | if(argc > 2): 39 | MAJOR_VERSION = sys.argv[2] 40 | if(argc > 3): 41 | MINOR_VERSION = sys.argv[3] 42 | if(argc > 4): 43 | FIX_VERSION = sys.argv[4] 44 | # \r\n will have trouble with $ to match end 45 | pattern_rc = re.compile( 46 | r'^VALUE "ProductVersion", "\d+, \d+, \d+, \d+[\\]0"', re.M) 47 | rc_string = r'VALUE "ProductVersion", "{}, {}, {}, {}\\0"'.format( 48 | MAJOR_VERSION, MINOR_VERSION, FIX_VERSION, build_version_number) # .encode('utf-8') 49 | 50 | pattern2_rc = re.compile(r'^VALUE "FileVersion", "\d+, \d+, \d+, \d+[\\]0"', re.M) 51 | rc_string2 = r'VALUE "FileVersion", "{}, {}, {}, {}\\0"'.format( 52 | MAJOR_VERSION, MINOR_VERSION, FIX_VERSION, build_version_number) 53 | 54 | pattern3_rc = re.compile(r'^FILEVERSION \d+, \d+, \d+, \d+', re.M) 55 | rc_string3 = r'FILEVERSION {}, {}, {}, {}'.format( 56 | MAJOR_VERSION, MINOR_VERSION, FIX_VERSION, build_version_number) 57 | 58 | pattern4_rc = re.compile(r'^PRODUCTVERSION \d+, \d+, \d+, \d+', re.M) 59 | rc_string4 = r'PRODUCTVERSION {}, {}, {}, {}'.format( 60 | MAJOR_VERSION, MINOR_VERSION, FIX_VERSION, build_version_number) 61 | 62 | pattern_stdafx_h = re.compile( 63 | r'^#define VERSION_NUMS L"\d+[.]\d+[.][0-9b-]+"', re.M) 64 | stdafx_h_string = r'#define VERSION_NUMS L"{}.{}.{}-b{}"'.format( 65 | MAJOR_VERSION, MINOR_VERSION, FIX_VERSION, build_version_number) # .encode('utf-16le') 66 | 67 | base_dir = os.path.dirname(os.path.abspath(__file__)) 68 | rc_file = os.path.join(base_dir, "CommandTrayHost", "CommandTrayHost.rc") 69 | stdafx_h_file = os.path.join(base_dir, "CommandTrayHost", "stdafx.h") 70 | 71 | print(stdafx_h_string, '\n', rc_string, '\n', rc_string2) 72 | 73 | for file_name, pattern_re, replace_string, encoding in ( 74 | (stdafx_h_file, pattern_stdafx_h, stdafx_h_string, 'utf-8'), 75 | (rc_file, pattern_rc, rc_string, 'utf-16le'), 76 | (rc_file, pattern2_rc, rc_string2, 'utf-16le'), 77 | (rc_file, pattern3_rc, rc_string3, 'utf-16le'), 78 | (rc_file, pattern4_rc, rc_string4, 'utf-16le'), 79 | ): 80 | # CommandTrayHost.rc # stdafx.h 81 | print(f'opening {file_name}') 82 | try: 83 | with open(file_name, "rb") as f: 84 | content = f.read() 85 | except: 86 | print(f"open {file_name} failed!") 87 | return False 88 | print(len(content), end=" ") 89 | 90 | detect = chardet.detect(content) 91 | print(detect, end=' ') 92 | encoding = detect['encoding'] 93 | 94 | if encoding.lower().startswith('utf-16'): 95 | bom = codecs.BOM_UTF16_LE # ''.encode('utf-16') 96 | elif encoding.lower().startswith('utf-8'): 97 | bom = codecs.BOM_UTF8 # ''.encode('utf-8-sig') 98 | 99 | if content.startswith(bom): 100 | print("bom:", repr(bom), end=' ') 101 | # content = content[len(bom):] 102 | 103 | try: 104 | content = content.decode(encoding) 105 | except: 106 | traceback.print_exc() 107 | print("binary.decode failed!") 108 | 109 | if pattern_re.search(content) is None: 110 | print("search failed") 111 | return False 112 | print(len(content), end=" sub-> ") 113 | content = pattern_re.sub(replace_string, content) 114 | if file_name.endswith("stdafx.h"): 115 | content = content.replace(r'#define BUILD_TIME_CN __TIMESTAMP__', 116 | f'#define BUILD_TIME_CN "{local_time()}"' 117 | ) 118 | content = content.replace(r'#define BUILD_TIME_EN __TIMESTAMP__', 119 | f'#define BUILD_TIME_EN "{local_time("UTC")}"' 120 | ) 121 | print(len(content), end=" ") 122 | 123 | if file_name.endswith(".rc"): 124 | encoding = 'utf-16' 125 | 126 | if encoding == 'utf-16le': 127 | content = bom + content.encode(encoding) 128 | else: 129 | content = content.encode(encoding) 130 | 131 | print(len(content), encoding) 132 | with open(file_name, "wb") as f: 133 | f.write(content) 134 | print(file_name, "repalced!\n") 135 | 136 | return True 137 | 138 | 139 | if __name__ == '__main__': 140 | print(f"""{__file__} [MAJOR_VERSION] [MINOR_VERSION] [FIX_VERSION] 141 | [MAJOR_VERSION] [MINOR_VERSION] [FIX_VERSION] are optional.""") 142 | print(sys.version_info) 143 | if (sys.version_info < (3, 0)): 144 | sys.exit(2) 145 | if not main(): 146 | sys.exit(1) 147 | -------------------------------------------------------------------------------- /CommandTrayHost/language.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "language.h" 3 | //#include "configure.h" 4 | #include "language_data.h" 5 | #include "utils.hpp" 6 | #include 7 | 8 | 9 | //extern nlohmann::json global_stat; 10 | extern CHAR locale_name[LOCALE_NAME_MAX_LENGTH]; 11 | extern BOOL isZHCN, isENUS; 12 | 13 | std::string translate(const std::string& en) 14 | { 15 | if (language_data == nullptr)return en; 16 | /*char lang[10]; 17 | snprintf(lang, 8, "0x%04x", language_code); 18 | LOGMESSAGE(L"lang: %S\n", lang);*/ 19 | 20 | if (false == json_object_has_member(language_data, locale_name) || 21 | false == json_object_has_member(language_data[locale_name], en.c_str()) 22 | ) 23 | { 24 | return en; 25 | } 26 | LOGMESSAGE(L"success: %s -> %s\n", 27 | utf8_to_wstring(en).c_str(), 28 | utf8_to_wstring(language_data[locale_name][en.c_str()]).c_str() 29 | ); 30 | return language_data[locale_name][en.c_str()]; 31 | } 32 | 33 | std::wstring translate_w2w(const std::wstring& en) 34 | { 35 | if (isENUS)return en; 36 | return utf8_to_wstring(translate(wstring_to_utf8(en))); 37 | } 38 | 39 | void update_isZHCN(bool check_system_acp) 40 | { 41 | if (0 == strcmp(locale_name, "zh-CN") || 42 | 0 == strcmp(locale_name, "zh-Hans") || 43 | 0 == strcmp(locale_name, "zh") || 44 | 0 == strcmp(locale_name, "zh-SG") 45 | ) 46 | { 47 | isZHCN = TRUE; 48 | } 49 | else 50 | { 51 | isZHCN = FALSE; 52 | } 53 | if (FALSE == isZHCN && true == check_system_acp) 54 | { 55 | // GetSystemDefaultLCID https://msdn.microsoft.com/en-us/library/windows/desktop/dd318693(v=vs.85).aspx 56 | // GetACP https://msdn.microsoft.com/en-us/library/windows/desktop/dd317756(v=vs.85).aspx 57 | // I think that GetACP is better than GetUserDefaultLocaleName 58 | isZHCN = GetSystemDefaultLCID() == 2052 || GetACP() == 936; 59 | } 60 | } 61 | 62 | void update_locale_name_by_alias() 63 | { 64 | if (json_object_has_member(language_alias, locale_name)) 65 | { 66 | std::string locale_alias = language_alias[locale_name]; 67 | // strcpy_s(locale_name, LOCALE_NAME_MAX_LENGTH, locale_alias.c_str()); 68 | if (FAILED(StringCchCopyA(locale_name, ARRAYSIZE(locale_name), locale_alias.c_str()))) 69 | { 70 | LOGMESSAGE(L"StringCchCopyA Failed\n"); 71 | } 72 | LOGMESSAGE(L"locale_alias:%S\n", locale_alias); 73 | } 74 | else 75 | { 76 | LOGMESSAGE(L"locale_name not found!\n"); 77 | } 78 | } 79 | 80 | void update_locale_name_by_system() 81 | { 82 | #if VER_PRODUCTBUILD == 7600 83 | PCSTR locale_pointer = NULL; 84 | if (GetSystemDefaultLCID() == 2052 || GetACP() == 936) 85 | { 86 | locale_pointer = "zh-CN"; 87 | } 88 | else 89 | { 90 | locale_pointer = "en-US"; 91 | } 92 | if (FAILED(StringCchCopyA(locale_name, ARRAYSIZE(locale_name), locale_pointer))) 93 | { 94 | LOGMESSAGE(L"update_locale_name_by_alias StringCchCopyA Failed\n"); 95 | } 96 | #else 97 | WCHAR wlocale_name[LOCALE_NAME_MAX_LENGTH]; 98 | if (GetUserDefaultLocaleName(wlocale_name, LOCALE_NAME_MAX_LENGTH)) 99 | { 100 | LOGMESSAGE(L"GetUserDefaultLocaleName %s\n", wlocale_name); 101 | // strcpy_s(locale_name, LOCALE_NAME_MAX_LENGTH, wstring_to_utf8(wlocale_name).c_str()); 102 | if (FAILED(StringCchCopyA(locale_name, ARRAYSIZE(locale_name), wstring_to_utf8(wlocale_name).c_str()))) 103 | { 104 | LOGMESSAGE(L"StringCchCopyA Failed\n"); 105 | } 106 | //update_isZHCN(); 107 | } 108 | else 109 | { 110 | //isZHCN = GetSystemDefaultLCID() == 2052; 111 | LOGMESSAGE(L"initialize_local failed!\n"); 112 | } 113 | #endif 114 | } 115 | 116 | void initialize_local(bool has_lang, PCSTR lang_str) 117 | { 118 | LOGMESSAGE(L"GetUserDefaultUILanguage: 0x%x GetSystemDefaultUILanguage: 0x%0x GetACP: %d\n", 119 | GetUserDefaultUILanguage(), 120 | GetSystemDefaultUILanguage(), 121 | GetACP() 122 | ); 123 | LOGMESSAGE(L"has_lang:%d lang_str:%S\n", has_lang, lang_str); 124 | //if (false == json_object_has_member(global_stat, "lang") || global_stat["lang"] == "auto") 125 | if (false == has_lang || 0 == strcmp(lang_str, "auto")) 126 | { 127 | update_locale_name_by_system(); 128 | update_locale_name_by_alias(); 129 | update_isZHCN(true); //use system acp check 130 | } 131 | else 132 | { 133 | //assert(json_object_has_member(global_stat, "lang")); 134 | assert(lang_str); 135 | //if (json_object_has_member(global_stat, "lang")) // it must be sure 136 | { 137 | std::string local = lang_str; 138 | // strcpy_s(locale_name, LOCALE_NAME_MAX_LENGTH, local.c_str()); 139 | if (FAILED(StringCchCopyA(locale_name, ARRAYSIZE(locale_name), local.c_str()))) 140 | { 141 | LOGMESSAGE(L"StringCchCopyA Failed\n"); 142 | } 143 | } 144 | update_locale_name_by_alias(); 145 | update_isZHCN(false); //use user defined 146 | } 147 | LOGMESSAGE(L"final locale_name %S\n", locale_name); 148 | 149 | /*if (json_object_has_member(global_stat, "lang")) 150 | { 151 | std::string local = global_stat["lang"]; 152 | //wcscpy(local_name, utf8_to_wstring(local).c_str()); 153 | strcpy_s(locale_name, LOCALE_NAME_MAX_LENGTH, local.c_str()); 154 | if (0 == strcmp(locale_name, "auto")) 155 | { 156 | update_locale_name_by_system(); 157 | update_locale_name_by_alias(); 158 | update_isZHCN(true); //use system acp check 159 | } 160 | else 161 | { 162 | update_locale_name_by_alias(); 163 | update_isZHCN(false); //use user defined 164 | } 165 | LOGMESSAGE(L"json_object_has_member %S\n", locale_name); 166 | } 167 | else // no lang items 168 | { 169 | update_locale_name_by_system(); 170 | update_locale_name_by_alias(); 171 | update_isZHCN(true); //use system acp check 172 | }*/ 173 | 174 | if (isZHCN == FALSE && (0 == strcmp(locale_name, "en-US") || 175 | false == json_object_has_member(language_data, locale_name)) 176 | ) 177 | { 178 | isENUS = TRUE; 179 | } 180 | else 181 | { 182 | isENUS = FALSE; 183 | } 184 | LOGMESSAGE(L"locale_name: %S isZHCN: %d isENUS: %d\n", locale_name, isZHCN, isENUS); 185 | // BOOL isZHCN = GetSystemDefaultLCID() == 2052; 186 | extern WCHAR szBalloon[512]; 187 | if (not isZHCN) 188 | { 189 | ZeroMemory(szBalloon, sizeof(szBalloon)); 190 | if (FAILED(StringCchCopy(szBalloon, 191 | ARRAYSIZE(szBalloon), 192 | translate_w2w(L"CommandTrayHost Started,Click Tray icon to Hide/Show Console.").c_str())) 193 | ) 194 | { 195 | LOGMESSAGE(L"init_global StringCchCopy failed\n"); 196 | } 197 | // wcscpy_s(szBalloon, translate_w2w(L"CommandTrayHost Started,Click Tray icon to Hide/Show Console.").c_str()); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /CommandTrayHost/cron.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "cron.h" 3 | #include "utils.hpp" 4 | #include "CommandTrayHost.h" 5 | #include "configure.h" 6 | #include "cache.h" 7 | 8 | extern nlohmann::json* global_cache_configs_pointer; 9 | extern nlohmann::json* global_configs_pointer; 10 | 11 | extern bool enable_cache; 12 | extern bool disable_cache_show; 13 | 14 | /*void to_json(nlohmann::json& j, const cron_expr& p) { 15 | j = nlohmann::json{ std::string(reinterpret_cast(&p), sizeof(cron_expr)) }; 16 | } 17 | 18 | void from_json(const nlohmann::json& j, cron_expr& p) { 19 | memcpy(reinterpret_cast(&p), j.get().data(), sizeof(cron_expr)); 20 | } 21 | 22 | cron_expr* get_cron_expr(const nlohmann::json& jsp, cron_expr& result) 23 | { 24 | if (json_object_has_member(jsp, "crontab_config") && json_object_has_member(jsp["crontab_config"], "cron_expr")) 25 | { 26 | result = jsp["crontab_config"]["cron_expr"]; 27 | return &result; 28 | } 29 | return nullptr; 30 | }*/ 31 | 32 | void rotate_file(PCWSTR filename) 33 | { 34 | TCHAR buffer[MAX_PATH * 10]; 35 | for (int i = 1; i < 500; i++) 36 | { 37 | StringCchPrintf(buffer, ARRAYSIZE(buffer), L"%s.%d", filename, i); 38 | if (TRUE != PathFileExists(buffer)) 39 | { 40 | if (MoveFile(filename, buffer)) 41 | { 42 | 43 | } 44 | else 45 | { 46 | msg_prompt( 47 | L"cannot rename filename!", 48 | L"Logrotate failed!", 49 | MB_OK 50 | ); 51 | } 52 | return; 53 | } 54 | } 55 | msg_prompt(L"There are too many log files, Please delete or move them elsewhere.", L"Logrotate error", MB_OK); 56 | } 57 | 58 | /* 59 | * Make sure jsp has "crontab_config", before call crontab_log 60 | * @param time_cur,current time. 61 | * @param time_next,next schedule time. If both are 0, just output log_msg & cron_msg 62 | */ 63 | void crontab_log(const nlohmann::json& jsp_crontab_config, 64 | time_t time_cur, 65 | time_t time_next, 66 | PCSTR name, 67 | PCSTR log_msg, 68 | PCSTR cron_msg, 69 | int log_count, 70 | int log_level_limit 71 | ) 72 | { 73 | //if (json_object_has_member(jsp, "crontab_config")) 74 | { 75 | //auto& crontab_config_ref = jsp["crontab_config"]; 76 | int log_level = jsp_crontab_config.value("log_level", 0); 77 | if (log_level < log_level_limit) { return; } 78 | const size_t buffer_len = 256; 79 | char buffer[buffer_len]; 80 | size_t idx = 0, len; 81 | tm t1; 82 | bool is_crontab_trigger_msg = true; 83 | if (time_cur == 0) 84 | { 85 | time_cur = time(NULL); 86 | if (0 == time_next)is_crontab_trigger_msg = false; 87 | } 88 | localtime_s(&t1, &time_cur); 89 | idx = strftime(buffer, ARRAYSIZE(buffer), "%Y-%m-%d %H:%M:%S ", &t1); 90 | if (is_crontab_trigger_msg) 91 | { 92 | printf_to_bufferA(buffer, buffer_len - idx, idx, 93 | "[%s] [%s] [left count: %d] [%s]", 94 | name, 95 | log_msg, 96 | log_count, 97 | //log_count == 0 ? " infinite" : "", 98 | cron_msg 99 | ); 100 | if (time_next) 101 | { 102 | localtime_s(&t1, &time_next); 103 | len = strftime(buffer + idx, ARRAYSIZE(buffer), " %Y-%m-%d %H:%M:%S ", &t1); 104 | idx += len; 105 | } 106 | } 107 | else 108 | { 109 | printf_to_bufferA(buffer, buffer_len - idx, idx, 110 | "[%s] [%s] [%s]", 111 | name, 112 | log_msg, 113 | cron_msg 114 | ); 115 | } 116 | 117 | std::string crontab_log_filename = jsp_crontab_config["log"]; 118 | std::wstring crontab_log_filename_w = utf8_to_wstring(crontab_log_filename); 119 | if (TRUE == PathFileExists(crontab_log_filename_w.c_str()) && FileSize(crontab_log_filename_w.c_str()) > 1024 * 1024 * 10) 120 | { 121 | rotate_file(crontab_log_filename_w.c_str()); 122 | } 123 | std::ofstream o_log(crontab_log_filename.c_str(), std::ios_base::app | std::ios_base::out); 124 | o_log << buffer << std::endl; 125 | } 126 | } 127 | 128 | cron_expr* get_cron_expr(const nlohmann::json& jsp, cron_expr& result) 129 | { 130 | if (json_object_has_member(jsp, "crontab_config")) 131 | { 132 | auto& crontab_config_ref = jsp["crontab_config"]; 133 | if (crontab_config_ref["enabled"]) 134 | { 135 | //cron_expr expr; 136 | ZeroMemory(&result, sizeof(cron_expr)); // if not do this, always get incorrect result 137 | const char* err = NULL; 138 | cron_parse_expr(crontab_config_ref["crontab"].get().c_str(), &result, &err); 139 | if (err) 140 | { 141 | LOGMESSAGE(L"cron_parse_expr failed! %S\n", err); 142 | } 143 | else 144 | { 145 | return &result; 146 | } 147 | } 148 | } 149 | return nullptr; 150 | } 151 | 152 | 153 | void handle_crontab(size_t idx) 154 | { 155 | auto& config_i_ref = (*global_configs_pointer)[idx]; // ["crontab_config"] 156 | if (json_object_has_member(config_i_ref, "crontab_config")) 157 | { 158 | auto& crontab_ref = config_i_ref["crontab_config"]; 159 | extern HWND hWnd; 160 | KillTimer(hWnd, VM_TIMER_BASE + idx); 161 | 162 | //bool crontab_write_log = false; 163 | time_t log_time_cur = time(NULL), log_time_next = 0; 164 | PCSTR log_msg = nullptr, log_cron_msg = nullptr; 165 | int log_count = 0; 166 | std::string crontab_method = crontab_ref["method"]; 167 | bool need_renew = crontab_ref["need_renew"]; 168 | extern HANDLE ghJob; 169 | bool enable_cache_backup = enable_cache; 170 | enable_cache = false; 171 | 172 | bool to_start; 173 | if (false == need_renew) 174 | { 175 | if (crontab_method.compare(0, ARRAYSIZE("start") - 1, "start") == 0) 176 | { 177 | to_start = true; 178 | 179 | if (config_i_ref["running"]) 180 | { 181 | int64_t handle = config_i_ref["handle"]; 182 | DWORD lpExitCode; 183 | BOOL retValue = GetExitCodeProcess(reinterpret_cast(handle), &lpExitCode); 184 | if (retValue != 0 && lpExitCode == STILL_ACTIVE) 185 | { 186 | to_start = false; 187 | log_msg = "method:start program is still running."; 188 | } 189 | } 190 | if (to_start) 191 | { 192 | /*config_i_ref["enabled"] = true; 193 | create_process(config_i_ref, ghJob, true);*/ 194 | log_msg = "method:start started."; 195 | //crontab_write_log = true; 196 | } 197 | } 198 | /*else if (crontab_method == "restart") 199 | { 200 | 201 | } 202 | else if (crontab_method == "stop")*/ 203 | else 204 | { 205 | to_start = false; 206 | if (config_i_ref["enabled"] && config_i_ref["running"] && config_i_ref["en_job"]) 207 | { 208 | disable_enable_menu(config_i_ref, ghJob); 209 | log_msg = "method:stop killed."; 210 | //crontab_write_log = true; 211 | } 212 | if (crontab_method.compare(0, ARRAYSIZE("restart") - 1, "restart") == 0) // should I minus 1 213 | { 214 | to_start = true; 215 | /*config_i_ref["enabled"] = true; 216 | create_process(config_i_ref, ghJob, false, true);*/ 217 | log_msg = "method:restart done."; 218 | //crontab_write_log = true; 219 | } 220 | } 221 | if (to_start) 222 | { 223 | config_i_ref["enabled"] = true; 224 | bool start_show = false, config_i_start_show_backup = false; 225 | if (json_object_has_member(crontab_ref, "start_show")) 226 | { 227 | start_show = crontab_ref["start_show"]; 228 | } 229 | else 230 | { 231 | if (enable_cache_backup && !disable_cache_show) 232 | { 233 | auto& ref = (*global_cache_configs_pointer)[idx]; 234 | if (check_cache_valid(ref["valid"].get(), cShow)) 235 | { 236 | start_show = ref["start_show"]; 237 | LOGMESSAGE(L"start_show cache hit!"); 238 | } 239 | } 240 | } 241 | if (json_object_has_member(config_i_ref, "start_show")) 242 | { 243 | config_i_start_show_backup = config_i_ref["start_show"]; 244 | } 245 | config_i_ref["start_show"] = start_show; 246 | create_process(config_i_ref, ghJob, false, true); 247 | config_i_ref["start_show"] = config_i_start_show_backup; 248 | } 249 | } 250 | else 251 | { 252 | log_msg = "method:renew"; 253 | } 254 | enable_cache = enable_cache_backup; 255 | 256 | 257 | int crontab_count = crontab_ref["count"]; 258 | 259 | if (need_renew || crontab_count != 1) 260 | { 261 | cron_expr c; 262 | if (nullptr != get_cron_expr(config_i_ref, c)) 263 | { 264 | time_t next_t = 0, now_t = time(NULL); 265 | next_t = cron_next(&c, now_t); // return -1 when failed 266 | LOGMESSAGE(L"next_t %llu now_t %llu\n", next_t, now_t); 267 | if (next_t != static_cast(-1) && next_t > now_t) 268 | { 269 | log_time_next = next_t; // logging 270 | 271 | next_t -= now_t; 272 | if (next_t > CRONTAB_MAXIUM_SECONDS) 273 | { 274 | next_t = CRONTAB_RENEW_MARKER; 275 | if (!need_renew)crontab_ref["need_renew"] = true; 276 | } 277 | else if (need_renew) 278 | { 279 | crontab_ref["need_renew"] = false; 280 | } 281 | next_t *= 1000; 282 | //if (next_t > USER_TIMER_MAXIMUM)next_t = USER_TIMER_MAXIMUM; 283 | SetTimer(hWnd, VM_TIMER_BASE + idx, static_cast(next_t), NULL); 284 | 285 | log_cron_msg = "schedule next"; // logging 286 | 287 | if (false == need_renew && crontab_count > 1) 288 | { 289 | crontab_ref["count"] = crontab_count - 1; 290 | log_count = crontab_count - 1; 291 | } 292 | } 293 | } 294 | } 295 | else 296 | { 297 | crontab_ref["enabled"] = false; 298 | 299 | log_count = -1; 300 | // *_stop 301 | size_t crontab_method_len = crontab_method.length(); 302 | if (crontab_method.compare(crontab_method_len - ARRAYSIZE("_stop"), ARRAYSIZE("_stop"), "_stop") == 0) 303 | { 304 | config_i_ref["enabled"] = true; 305 | disable_enable_menu(config_i_ref, ghJob); 306 | log_cron_msg = "count limited,crontab stopped. program stopped."; 307 | } 308 | else 309 | { 310 | log_cron_msg = "count limited,crontab stopped."; 311 | } 312 | } 313 | if (json_object_has_member(crontab_ref, "log")) 314 | { 315 | crontab_log(crontab_ref, log_time_cur, log_time_next, config_i_ref["name"].get().c_str(), log_msg, log_cron_msg, log_count, 0); 316 | } 317 | } 318 | else 319 | { 320 | msg_prompt(L"Crontab has no crontab_config! Please report this windows screenshot to author!", 321 | L"Crontab Error", 322 | MB_OK 323 | ); 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /README.zh-cn.md: -------------------------------------------------------------------------------- 1 | # CommandTrayHost 2 | 3 | Windows命令行程序运行监控系统托盘管理工具 4 | 5 | [![Build status](https://ci.appveyor.com/api/projects/status/v5md4dc9q1oy6qxh?svg=true)](https://ci.appveyor.com/project/rexdf/commandtrayhost) 6 | 7 | [English](README.md) 8 | 9 | # 特性 10 | 11 | - json配置文件 12 | - 系统托盘 13 | - 支持以管理员运行 14 | - 显示隐藏命令行界面,方便查看日志 启动禁用管理 15 | - 可以配置任意多数量的(几十个应该没啥问题)后台命令行 16 | - 当CommandTrayHost退出时,由操作系统保证清理所有的子进程。 17 | - 自定义托盘图标和命令行图标 18 | - 本地化支持 19 | - 自定义菜单层级最多支持40级 20 | - 多实例运行与开机启动支持 21 | - 热键支持 22 | - Crontab计划任务 23 | 24 | # 使用 25 | 26 | [下载](https://github.com/rexdf/CommandTrayHost/releases) or [镜像](https://api.rexdf.org/download/rexdf/CommandTrayHost/releases/download/) 27 | 28 | 使用前可以先看一眼[wiki](https://github.com/rexdf/CommandTrayHost/wiki) 29 | 30 | 配置文件名必须是`config.json`,必须放到CommandTrayHost.exe所在目录。**运行一次,会自动生成一个基本的`config.json`模板(包括详细文档)**。支持的编码为UTF8 UTF-16LE UTF-16BE UTF-32等,支持BOM识别。也就是支持记事本保存的Unicode和UTF-8格式。 31 | 32 | 配置样例 33 | 34 | ```javascript 35 | { 36 | "configs": [ 37 | { 38 | // 下面8个一个不能少 39 | "name": "kcptun 1080 8.8.8.1:12345", // 系统托盘菜单名字 40 | "path": "E:\\program\\kcptun-windows-amd64", // cmd的exe所在目录,相对路径是可以的,参考目录是CommandTrayHost.exe所在目录 41 | "cmd": "client_windows_amd64.exe -c client.json", // cmd命令,必须含有.exe 42 | "working_directory": "", // 命令行的工作目录,比如这里的client.json,如果是相对路径,>开头意味着相对于CommandTrayHost.exe,否则相对于path。 43 | "addition_env_path": "", // dll搜索目录,暂时没用到 44 | "use_builtin_console": false, // 是否用CREATE_NEW_CONSOLE,暂时没用到 45 | "is_gui": false, // 是否是 GUI图形界面程序,暂时没用到 46 | "enabled": true, // 是否当CommandTrayHost启动时,自动开始运行 47 | // 下面的是可选参数 48 | "require_admin": false, // 是否要用管理员运行,当CommandTrayHost不是以管理员运行的情况下,显示/隐藏会失效,其他功能正常。 49 | "start_show": false, // 是否以显示(而不是隐藏)的方式启动子程序 50 | "ignore_all": false, // 是否忽略全部启用禁用操作。当为true时,全部启用菜单对本程序无效 51 | "position": [ // 显示窗口的初始位置 52 | 0.2, // STARTUPINFO.dwX 大于1就是数值,0-1之间的数值代表相对屏幕分辨率的百分比 53 | 200 // STARTUPINFO.dwY, 同上 54 | ], 55 | "size": [ // 显示窗口的初始大小 56 | 0.5, // STARTUPINFO.dwXSize, 同上 57 | 0.5 // STARTUPINFO.dwYSize, 同上 58 | ], 59 | "icon": "", // 命令行窗口的图标 60 | "alpha": 170, // 命令行窗口的透明度,0-255之间的整数,0为完全看不见,255完全不透明 61 | "topmost": false, // 命令行窗口置顶 62 | // 可以使用的有alt win shift ctrl 0-9 A-Z 空格或者+号分割 63 | // 或者使用这种形式 "ALT+WIN+CTRL+0x20" 鼠标手柄的键盘码参考 64 | // https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx 65 | "hotkey": { // 下面并不需要都出现,可以只设置部分 66 | "disable_enable": "Shift+Win+D", // 启用/禁用切换 67 | "hide_show": "Shift+Win+H", // 显示/隐藏切换 68 | "restart": "Shift+Win+R", // 重启程序 69 | "elevate": "Shift+Win+A", // 以管理员运行本程序 70 | }, 71 | "not_host_by_commandtrayhost": false, // 如果设置成了true,那么CommandTrayHost就不会监控它的运行了 72 | "not_monitor_by_commandtrayhost": false, // 如果设置成true同上,但是会随着CommandTrayHost退出而关闭。 73 | "kill_timeout": 200, // 执行关闭操作时,先尝试通知程序自己关闭然后等多少ms,然后再杀进程,默认是200ms 74 | "exclusion_id": 1, // 互斥id,要求是大于0的整数。相同的互斥id启动时,会先杀掉其他 75 | "kill_process_tree": false, // 杀进程的时候同时杀掉其子进程,用于nginx. 为true时,kill_timeout无效 76 | }, 77 | { 78 | "name": "kcptun 1081 8.8.8.1:12346", 79 | "path": "E:\\program\\kcptun-windows-amd64", 80 | "cmd": "client_windows_amd64.exe -c client.json", 81 | "working_directory": "E:\\program\\kcptun-windows-amd64\\config2", 82 | "addition_env_path": "", 83 | "use_builtin_console": false, 84 | "is_gui": false, 85 | "enabled": true 86 | // 可选 87 | "crontab_config": { // crontab配置 88 | "crontab": "8 */2 15-16 29 2 *", // crontab语法具体参考Linux 89 | "method": "start", // 支持的有 start restart stop start_count_stop restart_count_stop,最后两个表示count次数的最后一个会执行stop 90 | "count": 0, // 0 表示不只限制,大于0的整数,表示运行多少次就不运行了 91 | // 可选 92 | "enabled": true, 93 | "log": "commandtrayhost.log", // 日志文件名,注释掉本行就禁掉log了 94 | "log_level": 0, // log级别,缺省默认为0。0为仅仅记录crontab触发记录,1附加启动时的信息,2附加下次触发的信息 95 | "start_show": false, // 注释掉的话,使用cache值(如果有),cache禁用的状态下的默认值是false 96 | }, 97 | "exclusion_id": 1, 98 | }, 99 | { 100 | "name": "herokuapp", 101 | "path": "C:\\Program Files\\nodejs", 102 | "cmd": "node.exe local.js -s yousecret-id.herokuapp.com -l 1090 -m camellia-256-cfb -k ItsATopSecret -r 80", 103 | "working_directory": "E:\\program\\shadowsocks-heroku.git", // 用了一个不同的工作目录 104 | "addition_env_path": "", 105 | "use_builtin_console": false, 106 | "is_gui": false, 107 | "enabled": true 108 | }, 109 | { 110 | "name": "shadowsocks", 111 | "path": "E:\\program\\shadowsocks", 112 | "cmd": "Shadowsocks.exe", 113 | "working_directory": "", 114 | "addition_env_path": "", 115 | "use_builtin_console": false, 116 | "is_gui": true, 117 | "enabled": false 118 | }, 119 | { 120 | "name": "cow", 121 | "path": "E:\\program\\cow", 122 | "cmd": "cow.exe", 123 | "working_directory": "", 124 | "addition_env_path": "", 125 | "use_builtin_console": false, 126 | "is_gui": false, 127 | "enabled": true 128 | }, 129 | { 130 | "name": "aria2", 131 | "path": "E:\\program\\aria2-win-64bit", 132 | "cmd": "aria2c.exe --conf=aria2.conf", 133 | "working_directory": "", 134 | "addition_env_path": "", 135 | "use_builtin_console": false, 136 | "is_gui": false, 137 | "enabled": true, 138 | "kill_timeout": 2000, // 最多等2秒钟,让aria2有足够的时间保存session, 根据硬盘速度可适当增加,比如移动硬盘最好4秒 139 | }, 140 | { 141 | "name": "JeliLicenseServer", 142 | "path": "E:\\program\\JeliLicenseServer", 143 | "cmd": "JeliLicenseServer_windows_amd64.exe -u admin -l 127.0.0.153", 144 | "working_directory": "", 145 | "addition_env_path": "", 146 | "use_builtin_console": false, 147 | "is_gui": false, 148 | "enabled": true 149 | }, 150 | ], 151 | "global": true, 152 | // 可选参数 153 | "require_admin": false, // 是否CommandTrayHost要对自身提权 154 | // 绝大部分情况不需要admin的,但是如果真的需要,自动启动应该会有问题,可以参考使用 https://stefansundin.github.io/elevatedstartup/ 155 | "icon": "E:\\icons\\Mahm0udwally-All-Flat-Computer.ico", // 自定义托盘图标路径,空为默认内置 256x256 156 | "icon_size": 256, // 256 32 16 157 | "cmd_menu_max_length": 0, // cmd和path最大字符串个数,0表示不限制,大于0整数表示超过之后用...标出 158 | "lang": "auto", // zh-CN en-US https://msdn.microsoft.com/en-us/library/cc233982.aspx 159 | "groups": [ // groups的值是一个数组,可以有两种类型,一种为数值,一种为object。object代表下级菜单。object必须有name字段 160 | { 161 | "name": "kcptun", // 分级菜单的名字 162 | "groups": [ 163 | 0, // 编号,是configs的编号。数组下标,从0开始 164 | 1, 165 | ], 166 | }, 167 | { 168 | "name": "shadowsocks", 169 | "groups": [ 170 | 3, // 顺序就是菜单顺序 171 | 2, 172 | 4, 173 | ], 174 | }, 175 | 5, 176 | 6, 177 | { 178 | "name": "empty test", // 可以没有groups,但是不能没有name 179 | }, 180 | ], 181 | "enable_groups": true, // 启用分组菜单 182 | "groups_menu_symbol": "+", // 分组菜单标志 183 | "left_click": [ 184 | 0, 185 | 1 186 | ], // 左键单击显示/隐藏程序 configs序号,从0开始. 空数组或者注释掉,则显示CommandTrayHost本体 187 | "enable_cache": true, // 启用cache 188 | "conform_cache_expire": true, // CommandTrayHost是否检查cache文件和配置文件,设为false时热加载被禁用 189 | "disable_cache_position": false, // 禁止缓存窗口位置 190 | "disable_cache_size": false, // 禁止缓存窗口大小 191 | "disable_cache_enabled": true, // 禁止缓存启用禁用状态 192 | "disable_cache_show": false, // 禁止缓存显示隐藏状态 193 | "disable_cache_alpha": false, // 禁止缓存透明度 (缓存时只对有alpha值的configs有作用) 194 | // 可以使用的有alt win shift ctrl 0-9 A-Z 空格或者+号分割 195 | // 或者使用这种形式 "ALT+WIN+CTRL+0x20" 鼠标手柄的键盘码参考 196 | // https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx 197 | "hotkey": { 198 | "disable_all": "Alt+Win+Shift+D", 199 | "enable_all": "Alt Win + Shift +E", 200 | "hide_all": "Alt+WIN+Shift+H", 201 | "show_all": "AlT Win Shift s", 202 | "restart_all": "ALT+Win+Shift+U", 203 | "elevate": "Alt+wIn+Shift+a", 204 | "exit": "Alt+Win+Shift+X", 205 | "left_click": "Alt+Win+Shift+L", 206 | "right_click": "Alt+Win+Shift+R", 207 | // 下面的五个快捷键,可以对外部程序生效 208 | "add_alpha": "Alt+Ctrl+Win+0x26", // 修改当前激活的任何窗口(要可能)透明度,不仅仅只对本程序托管的有效,其他程序也行 209 | "minus_alpha": "Alt+Ctrl+Win+0x28", //上面上箭头 这里下箭头 Alt+Ctrl+Win+↑↓ 210 | "topmost": "Alt+Ctrl+Win+T", // 切换当前窗口的置顶状态 211 | "hide_current": "Alt+Ctrl+Win+H", // 隐藏当前窗口,可以在托盘图标上找到对应项目 212 | "show_all_docked": "Alt+Ctrl+Win+S", // 显示所有被上面这个快捷键隐藏的窗口 213 | }, 214 | "repeat_mod_hotkey": false, // 是否长按算多次,Windows XP下面无效 215 | "global_hotkey_alpha_step": 5, // 上面透明度调节的幅度 216 | "show_hotkey_in_menu": true, // 在菜单后面加上成功注册的热键 217 | "enable_hotkey": true, 218 | "start_show_silent": true, // 启动的时候屏幕不会闪(也就是等到获取到窗口才显示) 219 | "auto_hot_reloading_config": false, // 这个为true时,相当于自动点击加载配置弹窗的否 220 | "auto_update": true, 221 | "skip_prerelease": true, 222 | "keep_update_history": false, // 是否保留自动更新时的临时文件 223 | } 224 | ``` 225 | 226 | **提示1**: `"cmd"`必须包含`.exe`.如果要运行批处理.bat, 可以使用 `cmd.exe /c`或者`cmd.exe /k`. 227 | 228 | **提示2**: 管理员比较复杂,如果不是真的需要。配置中不要出现任何`require_admin`。 229 | 简而言之: 230 | - 如果CommandTrayHost是以管理员运行的,那么启动的要求特权的子进程没啥问题,但是CommandTrayHost开机启动会比较麻烦,不能用菜单的那个。 231 | - 如果CommandTrayHost是以普通用户运行的,而且没有要求提权,但是 尝试启动了一个要求提权的程序 或者 对程序加上了`"require_admin":true,`, 那么运行时会弹出UAC,授权后是可以正常运行以及重启应用,但是启动后,非特权的CommandTrayHost是没法唤出显示的。 232 | 233 | **提示3**: icon制作可以参考 [这里](http://www.imagemagick.org/Usage/thumbnails/#favicon) ,或者从[iconarchive](http://www.iconarchive.com)下载。 234 | 235 | **注意**: 所有的路径,必须是`\\`分割的,这是因为json规定字符串,会自动转义`\`之后的字符。 236 | 237 | [使用样例](https://github.com/rexdf/CommandTrayHost/wiki/使用样例) 238 | 239 | # 如何编译 240 | 241 | 1. VS2015 Update3 或者 VS2017 (其实这是vcpkg的要求) 242 | 2. 安装 [vcpkg](https://github.com/Microsoft/vcpkg) 243 | 3. 为当前用户集成vcpkg,以管理员命令行运行(只要用管理员运行一次,以后就不需要管理员权限了) `vcpkg integrate install` 244 | 4. 安装 rapidjson 和 nlohmann::json. `vcpkg install rapidjson rapidjson:x64-windows nlohmann-json nlohmann-json:x64-windows` 245 | 5. 打开 `CommandTrayHost.sln`, 点击编译. 246 | 247 | 为了保证`resource.h`和`CommandTrayHost.rc`编码为UTF-16LE(UCS-2)带BOM,在`git clone`之前,可能需要在`%USERPROFILE%\.gitconfig`文件最后面(不存在新建一个)加上如下内容: 248 | 249 | ```ini 250 | [filter "utf16"] 251 | clean = iconv -f utf-16le -t utf-8 252 | smudge = iconv -f utf-8 -t utf-16le 253 | required 254 | ``` 255 | 256 | # 如何本地化 257 | 258 | 看这个文件 [CommandTrayHost/CommandTrayHost/language_data.h](https://github.com/rexdf/CommandTrayHost/blob/master/CommandTrayHost/language_data.h) 259 | 260 | # TODO 261 | 262 | - [ ] 现在一旦重启某个应用,那么之前的窗口就会被关掉,然后重新开启一个。这样之前的日志就丢失了。希望对每个应用,启动一个独立辅助Console,即使重新启动应用,历史日志(标准IO输出)依然可以保留。 `use_builtin_console`就是用来做这个用途的。可以参考的有 [ConEmu](https://github.com/Maximus5/ConEmu),看上去必须要注入子进程,将其标准IO导入到ConsoleHelper才行。 263 | 264 | - [ ] 尝试集成 [Elevated Startup](https://stefansundin.github.io/elevatedstartup/) 265 | 266 | - [ ] UIPI (User Interface Privilege Isolation) Bypass. `ChangeWindowMessageFilterEx` 267 | 268 | # 感谢 269 | 270 | [phuslu/taskbar](https://github.com/phuslu/taskbar) 271 | [@lirener](https://github.com/lirener) 272 | 273 | 274 | [i1]: https://github.com/rexdf/CommandTrayHost/issues/1 275 | -------------------------------------------------------------------------------- /CommandTrayHost/cache.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CommandTrayHost.h" 3 | #include "cache.h" 4 | #include "language.h" 5 | #include "utils.hpp" 6 | #include "configure.h" 7 | 8 | extern nlohmann::json global_stat; 9 | extern nlohmann::json* global_cache_configs_pointer; 10 | extern nlohmann::json* global_configs_pointer; 11 | extern size_t number_of_configs; 12 | 13 | extern bool enable_cache; 14 | extern bool conform_cache_expire; 15 | extern bool disable_cache_position; 16 | extern bool disable_cache_size; 17 | extern bool disable_cache_enabled; 18 | extern bool disable_cache_show; 19 | extern bool disable_cache_alpha; 20 | extern bool is_cache_valid; 21 | extern bool auto_hot_reloading_config; 22 | 23 | extern bool reload_config_with_cache; 24 | extern bool cachefile_invalid; 25 | 26 | extern BOOL isZHCN, isENUS; 27 | //extern CRITICAL_SECTION CriticalSection; 28 | //extern bool enable_critialsection; 29 | 30 | bool get_filetime(PCWSTR const filename, FILETIME& file_write_timestamp) 31 | { 32 | if (TRUE != PathFileExists(filename)) 33 | { 34 | msg_prompt(filename, L"File not exist"); 35 | return false; 36 | } 37 | HANDLE json_hFile = CreateFile(filename, GENERIC_READ, 38 | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 39 | FILE_ATTRIBUTE_NORMAL, NULL); 40 | if (!json_hFile)return false; 41 | bool ret = true; 42 | if (!GetFileTime(json_hFile, NULL, NULL, &file_write_timestamp))ret = false; 43 | CloseHandle(json_hFile); 44 | return ret; 45 | } 46 | 47 | int is_cache_not_expired2() 48 | { 49 | LOGMESSAGE(L"GetCurrentThreadId:%d\n", GetCurrentThreadId()); 50 | 51 | //assert(enable_cache); 52 | 53 | PCWSTR json_filename = CONFIG_FILENAMEW; 54 | PCWSTR cache_filename = CACHE_FILENAMEW; 55 | 56 | if (TRUE != PathFileExists(json_filename)) 57 | { 58 | /*extern HANDLE ghJob; 59 | extern HICON gHicon; 60 | if (NULL == init_global(ghJob, gHicon)) 61 | { 62 | return true; 63 | }*/ 64 | return 1; 65 | } 66 | if (TRUE != PathFileExists(cache_filename)) 67 | { 68 | return 2; 69 | } 70 | 71 | FILETIME json_write_timestamp, cache_write_timestamp; 72 | if (!get_filetime(json_filename, json_write_timestamp) || !get_filetime(cache_filename, cache_write_timestamp)) 73 | { 74 | return 3; 75 | } 76 | 77 | 78 | if (CompareFileTime(&json_write_timestamp, &cache_write_timestamp) >= 0) 79 | { 80 | cachefile_invalid = true; 81 | LOGMESSAGE(L"json_write_timestamp is later than cache_write_timestamp\n"); 82 | if (global_stat == nullptr) 83 | { 84 | // prompt when start CommandTrayHost 85 | int result; 86 | if (!auto_hot_reloading_config) 87 | { 88 | result = msg_prompt(//NULL, 89 | isZHCN ? 90 | L"config.json被编辑过了,缓存可能已经失效!\n\n选择 是 则清空缓存" 91 | L"\n\n选择 否 则保留缓存数据" 92 | : 93 | utf8_to_wstring(translate("You just edit config.json!\n\nChoose Yes to clear" 94 | " cache\n\nChoose No to keep expired cache.")).c_str(), 95 | isZHCN ? L"是否要清空缓存?" : utf8_to_wstring(translate("Clear cache?")).c_str(), 96 | MB_YESNO 97 | ); 98 | } 99 | else 100 | { 101 | result = IDNO; 102 | } 103 | reload_config_with_cache = IDNO == result; 104 | if (result == IDYES) 105 | { 106 | return 4; 107 | } 108 | } 109 | } 110 | else 111 | { 112 | cachefile_invalid = false; 113 | } 114 | reload_config_with_cache = true; 115 | return 0; 116 | } 117 | 118 | bool is_config_changed() 119 | { 120 | extern int volatile atom_variable_for_config; 121 | //static int atom_variable = 0; 122 | if (atom_variable_for_config)return false; 123 | atom_variable_for_config = 1; 124 | //Sleep(200); // wait a little while for windows finish writing files to disk. Otherwise, there will be multiple timestamp 125 | extern FILETIME config_last_timestamp; 126 | LOGMESSAGE(L"dwLowDateTime:%d dwHighDateTime:%d\n", config_last_timestamp.dwLowDateTime, config_last_timestamp.dwHighDateTime); 127 | FILETIME current_config_timestamp; 128 | if (get_filetime(CONFIG_FILENAMEW, current_config_timestamp)) 129 | { 130 | if (CompareFileTime(¤t_config_timestamp, &config_last_timestamp) != 0) 131 | { 132 | config_last_timestamp = current_config_timestamp; 133 | //atom_variable = 0; 134 | return true; 135 | } 136 | } 137 | atom_variable_for_config = 0; 138 | return false; 139 | } 140 | 141 | bool reload_config() 142 | { 143 | extern int volatile atom_variable_for_config; 144 | //static int atom_variable = 0; 145 | //if (atom_variable)return false; 146 | //atom_variable = 1; 147 | LOGMESSAGE(L"GetCurrentThreadId:%d\n", GetCurrentThreadId()); 148 | if (global_stat == nullptr) 149 | { 150 | LOGMESSAGE(L"global_stat is nullptr\n"); 151 | atom_variable_for_config = 0; 152 | return false; 153 | } 154 | assert(conform_cache_expire); 155 | { 156 | int result; 157 | if (!auto_hot_reloading_config) 158 | { 159 | result = msg_prompt(//NULL, 160 | isZHCN ? 161 | L"config.json被编辑过了,缓存可能已经失效!" L" 不想看到这个窗口,设置\"auto_hot_reloading_config\":true即可。" 162 | L"\n\n选择 是 重新加载配置,但是清空缓存(如果启用缓存),关闭全部运行中的程序" 163 | L"\n\n选择 否 重新加载配置,尽最大努力保留缓存(如果启用缓存),cmd path working_directory未修改的运行中的程序不会被关闭" 164 | L"\n\n选择 取消 下次启动CommandTrayHost才加载配置" 165 | : 166 | utf8_to_wstring(translate("You just edit config.json!\n\nChoose Yes to clear" 167 | " cache\n\nChoose No to keep expired cache.") + 168 | translate("\n\nChoose Cancel to do nothing")).c_str(), 169 | isZHCN ? L"是否要清空缓存?" : utf8_to_wstring(translate("Clear cache?")).c_str(), 170 | MB_YESNOCANCEL 171 | ); 172 | } 173 | else 174 | { 175 | result = IDNO; 176 | } 177 | if (IDCANCEL == result) 178 | { 179 | atom_variable_for_config = 0; 180 | return true; 181 | } 182 | 183 | reload_config_with_cache = IDNO == result; 184 | 185 | unregisterhotkey_killtimer_all(); 186 | 187 | if (IDYES == result) 188 | { 189 | /* // we donot need to delete cache, because of reload_config_with_cache false 190 | if (enable_cache && NULL == DeleteFile(CACHE_FILENAMEW)) 191 | { 192 | LOGMESSAGE(L"DeleteFile GetLastError:%d\n", GetLastError()); 193 | msg_prompt(L"Delete " CACHE_FILENAMEW L" Failed!", L"Delete failed", MB_OK); 194 | }*/ 195 | enable_cache = false; 196 | kill_all(); 197 | } 198 | 199 | extern HANDLE ghJob; 200 | extern HICON gHicon; 201 | if (NULL == init_global(ghJob, gHicon)) 202 | { 203 | atom_variable_for_config = 0; 204 | return false; 205 | } 206 | start_all(ghJob); 207 | DeleteTrayIcon(); 208 | ShowTrayIcon(CONFIG_FILENAMEW L" has been reloaded.", NIM_ADD); 209 | } 210 | atom_variable_for_config = 0; 211 | return true; 212 | } 213 | 214 | 215 | /* 216 | * type: -1 means cache_name invalid, return idx 217 | * 0 fatal error 218 | * 1 return from cache memory 219 | * 2 return from cache file 220 | * 3 return from config.json 221 | * 4 not found from above all, return idx 222 | */ 223 | rapidjson::SizeType get_cache_index( 224 | const rapidjson::Value& d_configs, 225 | const rapidjson::Value* d_cache_configs, 226 | const PCSTR cache_name, 227 | //PCSTR cache_item_name, 228 | const rapidjson::SizeType cache_size, 229 | const rapidjson::SizeType global_cache_size, 230 | const rapidjson::SizeType idx, 231 | int& type 232 | ) 233 | { 234 | type = -1; 235 | // if cache name is empty return idx 236 | if (cache_name == NULL || cache_name[0] == NULL) 237 | { 238 | return idx; 239 | } 240 | type = 0; 241 | // Why read both from cache memory and file, 242 | // because cache can be disable and enable during reloading 243 | // and cache file can be deleted during reloading 244 | // try to read from cache memory 245 | if (idx < global_cache_size) 246 | { 247 | type = 1; 248 | for (rapidjson::SizeType i = idx; i < global_cache_size; i++) 249 | { 250 | auto& global_cache_config_i_ref = (*global_cache_configs_pointer)[i]; 251 | if (global_cache_config_i_ref["name"] == cache_name) 252 | { 253 | return i; 254 | } 255 | } 256 | if (idx)for (rapidjson::SizeType i = 0; i < idx; i++) 257 | { 258 | auto& global_cache_config_i_ref = (*global_cache_configs_pointer)[i]; 259 | if (global_cache_config_i_ref["name"] == cache_name) 260 | { 261 | return i; 262 | } 263 | } 264 | } 265 | // try to read from cache file 266 | if (idx < cache_size) 267 | { 268 | type = 2; 269 | for (rapidjson::SizeType i = idx; i < cache_size; i++) 270 | { 271 | auto& d_cache_config_i_ref = (*d_cache_configs)[idx]; 272 | //assert(d_cache_config_i_ref.HasMember("name")); 273 | if (d_cache_config_i_ref.HasMember("name") && d_cache_config_i_ref["name"] == cache_name) 274 | { 275 | return i; 276 | } 277 | } 278 | if (idx)for (rapidjson::SizeType i = 0; i < idx; i++) 279 | { 280 | auto& d_cache_config_i_ref = (*d_cache_configs)[idx]; 281 | //assert(d_cache_config_i_ref.HasMember("name")); 282 | if (d_cache_config_i_ref.HasMember("name") && d_cache_config_i_ref["name"] == cache_name) 283 | { 284 | return i; 285 | } 286 | } 287 | } 288 | // try to get from just read config.json 289 | const rapidjson::SizeType _next_number_of_configs = d_configs.Size(); 290 | if (idx < _next_number_of_configs) 291 | { 292 | type = 3; 293 | for (rapidjson::SizeType i = idx; i < _next_number_of_configs; i++) 294 | { 295 | auto& global_config_i_ref = d_configs[i]; 296 | if (global_config_i_ref["name"] == cache_name) 297 | { 298 | #ifdef _DEBUG 299 | msg_prompt(L"it works!", L"global_config_i_ref[\"name\"] == cache_name"); 300 | #endif 301 | return i; 302 | } 303 | } 304 | if (idx)for (rapidjson::SizeType i = 0; i < idx; i++) 305 | { 306 | auto& global_config_i_ref = d_configs[i]; 307 | if (global_config_i_ref["name"] == cache_name) 308 | { 309 | return i; 310 | } 311 | } 312 | } 313 | type = 4; 314 | return idx; 315 | } 316 | 317 | 318 | void update_cache_enabled_start_show(bool enabled, bool start_show) 319 | { 320 | if (enable_cache) 321 | { 322 | if (false == disable_cache_enabled) 323 | { 324 | update_cache("enabled", enabled, cEnabled); 325 | } 326 | if (false == disable_cache_show) 327 | { 328 | update_cache("start_show", start_show, cShow); 329 | } 330 | } 331 | } 332 | 333 | void update_cache_position_size(HWND hWnd) 334 | { 335 | if (enable_cache && hWnd) 336 | { 337 | //if (false == disable_cache_show) 338 | //{ 339 | // update_cache("start_show", false, 3); 340 | //} 341 | if (!disable_cache_alpha) 342 | { 343 | BYTE alpha; 344 | if (get_alpha(hWnd, alpha, true)) 345 | { 346 | if (get_cache("alpha") != alpha) 347 | { 348 | update_cache("alpha", alpha, cAlpha); 349 | } 350 | } 351 | } 352 | if (false == disable_cache_position || false == disable_cache_size) 353 | { 354 | RECT rect = {}; 355 | if (NULL != GetWindowRect(hWnd, &rect)) 356 | { 357 | LOGMESSAGE(L"left:%d top:%d right:%d bottom:%d\n", rect.left, rect.top, rect.right, rect.bottom); 358 | if (get_cache("left") != rect.left) 359 | { 360 | update_cache("left", rect.left, cPosition); 361 | } 362 | if (get_cache("top") != rect.top) 363 | { 364 | update_cache("top", rect.top, cPosition); 365 | } 366 | /*if (false == disable_cache_position) 367 | { 368 | int left = get_cache("left"), top = get_cache("top"); 369 | } 370 | */ 371 | 372 | if (false == disable_cache_size) 373 | { 374 | if (get_cache("right") != rect.right) 375 | { 376 | update_cache("right", rect.right, cSize); 377 | } 378 | if (get_cache("bottom") != rect.bottom) 379 | { 380 | update_cache("bottom", rect.bottom, cSize); 381 | } 382 | } 383 | } 384 | } 385 | } 386 | } 387 | 388 | bool flush_cache(/*bool is_exit*/) 389 | { 390 | assert(enable_cache); 391 | assert(false == is_cache_valid); 392 | /*static int cache_write_cnt = 0; 393 | if (is_exit == false) 394 | { 395 | if (cache_write_cnt < 100) 396 | { 397 | cache_write_cnt++; 398 | return true; 399 | } 400 | else 401 | { 402 | cache_write_cnt = 0; 403 | } 404 | } 405 | else 406 | { 407 | #ifdef _DEBUG 408 | std::ofstream o("cache_write.txt"); 409 | o << "cache_write_cnt: " << cache_write_cnt << std::endl; 410 | #endif 411 | cache_write_cnt = 0; 412 | }*/ 413 | 414 | LOGMESSAGE(L"Now flush cache\n"); 415 | //return true; 416 | is_cache_valid = true; 417 | //if (is_cache_not_expired(true)) 418 | { 419 | std::ofstream o(CACHE_FILENAMEA); 420 | if (o.good()) 421 | { 422 | #ifdef _DEBUG 423 | o << global_stat["cache"].dump(4); 424 | #else 425 | o << global_stat["cache"]; 426 | #endif 427 | return true; 428 | } 429 | } 430 | 431 | return false; 432 | } 433 | 434 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CommandTrayHost 2 | A command line program monitor systray for Windows 3 | 4 | [![Build status](https://ci.appveyor.com/api/projects/status/v5md4dc9q1oy6qxh?svg=true)](https://ci.appveyor.com/project/rexdf/commandtrayhost) 5 | 6 | [简体中文](README.zh-cn.md) 7 | 8 | # Feature 9 | 10 | - json configure 11 | - systray 12 | - run privileged child 13 | - show/hide enable/disable daemon 14 | - multiple command line programs 15 | - when CommandTrayHost quits, all child processes will be killed. 16 | - Customize systray icon & console icon 17 | - i18n 18 | - Menu level limit is 40 19 | - Multiple instance of startup and running 20 | - Hotkey 21 | - Crontab 22 | 23 | # Usage 24 | 25 | [Download](https://github.com/rexdf/CommandTrayHost/releases) or [Mirrors](https://api.rexdf.org/download/rexdf/CommandTrayHost/releases/download/) 26 | 27 | configure file name is `config.json`, in the same folder as CommandTrayHost.exe. Run once `CommandTrayHost.exe`, there will be a `config.json` with document. Supported encodings, `UTF-8 UTF-8BOM UTF-16LE UTF-16BE UTF-32LE UTF32-BE`. 28 | 29 | example configure 30 | 31 | ```javascript 32 | { 33 | "configs": [ 34 | { 35 | "name": "kcptun 1080 8.8.8.1:12345", // Menu item name in systray 36 | "path": "E:\\program\\kcptun-windows-amd64", // path which includes cmd exe, relative path is ok. 37 | "cmd": "client_windows_amd64.exe -c client.json", // must contain .exe 38 | "working_directory": "", // working directory, for client.json path. relative to path usually. start with > symbol to use relative path from CommandTrayHost.exe 39 | "addition_env_path": "", //dll search path 40 | "use_builtin_console": false, //CREATE_NEW_CONSOLE 41 | "is_gui": false, 42 | "enabled": true, // run when CommandTrayHost starts 43 | // Optional 44 | "require_admin": false, // to run as administrator, problems keywords: User Interface Privilege Isolation 45 | "start_show": false, // whether to show when start process 46 | "ignore_all": false, // whether to ignore operation to disable/enable all 47 | "position": [ 48 | 0.2, // STARTUPINFO.dwX if not greater than 1, it means proportions to screen resolution. Greater than 1, value 49 | 200 // STARTUPINFO.dwY, same as above 50 | ], 51 | "size": [ 52 | 0.5, // STARTUPINFO.dwXSize, as above 53 | 0.5 // STARTUPINFO.dwYSize, as above 54 | ], 55 | "icon": "", // icon for console windows 56 | "alpha": 170, // alpha for console windows, 0-255 integer 57 | "topmost": false, // topmost for console windows 58 | // alt win shift ctrl 0-9 A-Z, seperated by space or +. You can also use "ALT+WIN+CTRL+0x20" 59 | // https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx 60 | "hotkey": { // you don't nedd to set up all 61 | "disable_enable": "Shift+Win+D", // disable/enable toggle 62 | "hide_show": "Shift+Win+H", // hide/show toggle 63 | "restart": "Shift+Win+R", // restart app 64 | "elevate": "Shift+Win+A", // elevate 65 | }, 66 | "not_host_by_commandtrayhost": false, // if true, commandtrayhost will not monitor it 67 | "not_monitor_by_commandtrayhost": false, // if true, same as above. But quit with CommandTrayHost 68 | "kill_timeout": 200, // post a message to let program quit itself, if timeout killing the process 69 | "exclusion_id": 1, // kill all configs with same `exclusion_id` before to run current config. greater than 0. 70 | "kill_process_tree": false, // kill all child process tree,for nginx 71 | }, 72 | { 73 | "name": "kcptun 1081 8.8.8.1:12346", 74 | "path": "E:\\program\\kcptun-windows-amd64", 75 | "cmd": "client_windows_amd64.exe -c client.json", 76 | "working_directory": "E:\\program\\kcptun-windows-amd64\\config2", 77 | "addition_env_path": "", 78 | "use_builtin_console": false, 79 | "is_gui": false, 80 | "enabled": true 81 | // Optional 82 | "crontab_config": { 83 | "crontab": "8 */2 15-16 29 2 *", 84 | "method": "start", // start restart stop start_count_stop restart_count_stop 85 | "count": 0, // times to run, 0 infinite 86 | // Optional 87 | "enabled": true, 88 | "log": "commandtrayhost.log", // comment out this line to disable logging 89 | "log_level": 0, // log level 0 1 2 90 | "start_show": false, // comment out to use cache 91 | }, 92 | "exclusion_id": 1, 93 | }, 94 | { 95 | "name": "herokuapp", 96 | "path": "C:\\Program Files\\nodejs", 97 | "cmd": "node.exe local.js -s yousecret-id.herokuapp.com -l 1090 -m camellia-256-cfb -k ItsATopSecret -r 80", 98 | "working_directory": "E:\\program\\shadowsocks-heroku.git", // We use a different working directory 99 | "addition_env_path": "", 100 | "use_builtin_console": false, 101 | "is_gui": false, 102 | "enabled": true 103 | }, 104 | { 105 | "name": "shadowsocks", 106 | "path": "E:\\program\\shadowsocks", 107 | "cmd": "Shadowsocks.exe", 108 | "working_directory": "", 109 | "addition_env_path": "", 110 | "use_builtin_console": false, 111 | "is_gui": true, 112 | "enabled": false 113 | }, 114 | { 115 | "name": "cow", 116 | "path": "E:\\program\\cow", 117 | "cmd": "cow.exe", 118 | "working_directory": "", 119 | "addition_env_path": "", 120 | "use_builtin_console": false, 121 | "is_gui": false, 122 | "enabled": true 123 | }, 124 | { 125 | "name": "aria2", 126 | "path": "E:\\program\\aria2-win-64bit", 127 | "cmd": "aria2c.exe --conf=aria2.conf", 128 | "working_directory": "", 129 | "addition_env_path": "", 130 | "use_builtin_console": false, 131 | "is_gui": false, 132 | "enabled": true, 133 | "kill_timeout": 2000, // wait 2 seconds for saving aria2.session 134 | }, 135 | { 136 | "name": "JeliLicenseServer", 137 | "path": "E:\\program\\JeliLicenseServer", 138 | "cmd": "JeliLicenseServer_windows_amd64.exe -u admin -l 127.0.0.251", 139 | "working_directory": "", 140 | "addition_env_path": "", 141 | "use_builtin_console": false, 142 | "is_gui": false, 143 | "enabled": true 144 | }, 145 | ], 146 | "global": true, 147 | // Optional 148 | "require_admin": false, // To Run CommandTrayHost as Administrator 149 | // If you set it to true, maybe you will need https://stefansundin.github.io/elevatedstartup/ to add startup support 150 | "icon": "E:\\icons\\Mahm0udwally-All-Flat-Computer.ico", // Customize Tray Icon path 151 | // when empty, builtin default icon will be used. 256x256 152 | "icon_size": 256, // 256 32 16 153 | "cmd_menu_max_length": 0, // maximium character limit for cmd and path item in menu. 0 infinite 154 | "lang": "auto", // zh-CN en-US etc https://msdn.microsoft.com/en-us/library/cc233982.aspx 155 | "groups": [ // groups is an array. Allowed element types are object and number. 156 | { 157 | "name": "kcptun", // object must have a name 158 | "groups": [ 159 | 0, // index of configs 160 | 1, 161 | ], 162 | }, 163 | { 164 | "name": "shadowsocks", 165 | "groups": [ 166 | 3, 167 | 2, 168 | 4, 169 | ], 170 | }, 171 | 5, 172 | 6, 173 | { 174 | "name": "empty test", // groups is optional for object, but name not. 175 | }, 176 | ], 177 | "enable_groups": true, 178 | "groups_menu_symbol": "+", 179 | "left_click": [ 180 | 0, 181 | 1 182 | ], // left click on tray icon, hide/show configs index. Empty to hide/show CommandTrayHost 183 | "enable_cache": true, 184 | "conform_cache_expire": true, // hot reloading will be disabled, when setting it to false 185 | "disable_cache_position": false, 186 | "disable_cache_size": false, 187 | "disable_cache_enabled": true, 188 | "disable_cache_show": false, 189 | "disable_cache_alpha": false, // (only work for configs with alpha) 190 | // alt win shift ctrl 0-9 A-Z, seperated by space or +. You can also use "ALT+WIN+CTRL+0x20" 191 | // https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx 192 | "hotkey": { 193 | "disable_all": "Alt+Win+Shift+D", 194 | "enable_all": "Alt Win + Shift +E", 195 | "hide_all": "Alt+WIN+Shift+H", 196 | "show_all": "AlT Win Shift s", 197 | "restart_all": "ALT+Win+Shift+U", 198 | "elevate": "Alt+wIn+Shift+A", 199 | "exit": "Alt+Win+Shift+X", 200 | "left_click": "Alt+Win+Shift+L", 201 | "right_click": "Alt+Win+Shift+R", 202 | // work for any program of current user 203 | "add_alpha": "Alt+Ctrl+Win+0x26", // add alpha of current windows 204 | "minus_alpha": "Alt+Ctrl+Win+0x28", // Alt+Ctrl+Win+↑↓ 205 | "topmost": "Alt+Ctrl+Win+T", // toggle topmost status 206 | "hide_current": "Alt+Ctrl+Win+H", // hide current window to tray menu 207 | "show_all_docked": "Alt+Ctrl+Win+S", // show all windows hiden by above hotkey 208 | }, 209 | "repeat_mod_hotkey": false, // not work for XP 210 | "global_hotkey_alpha_step": 5, 211 | "show_hotkey_in_menu": true, 212 | "enable_hotkey": true, 213 | "start_show_silent": true, 214 | "auto_hot_reloading_config": false, // if true, it's same as automatically clicking clear cache prompt No button 215 | "auto_update": true, 216 | "skip_prerelease": true, 217 | "keep_update_history": false, 218 | } 219 | ``` 220 | 221 | **Tips1**: `"cmd"` must contain `.exe`. If you want to run a bat, you can use `cmd.exe /c` or `cmd.exe /k`. 222 | 223 | **Tips2**: If you don't need privileged child, you can remove all `"require_admin"`. 224 | - CommandTrayHost run as an unprivileged user: you can run a privileged child process and restart it, but you cannot hide/show it. Because of User Interface Privilege Isolation. 225 | - CommandTrayHost run as Administrator, everthing should work as you want. But you cannot use the builtin startup management. 226 | 227 | **Tips3**: How to create ico format [Here](http://www.imagemagick.org/Usage/thumbnails/#favicon) Or download from [iconarchive](http://www.iconarchive.com). 228 | 229 | **Note**: All paths must be `"C:\\Windows"` but not `"C:\Windows"`. Json string will escape `\`. 230 | 231 | # How to Build 232 | 233 | 1. Install VS2015 Update 3 or VS 2017 234 | 2. Install [vcpkg](https://github.com/Microsoft/vcpkg) 235 | 3. Hook up user-wide integration, run (note: requires admin on first use) `vcpkg integrate install` 236 | 4. Install rapidjson and nlohmann::json. `vcpkg install rapidjson rapidjson:x64-windows nlohmann-json nlohmann-json:x64-windows` 237 | 5. Open `CommandTrayHost.sln`, and build. 238 | 239 | In order to make sure `resource.h` and `CommandTrayHost.rc` is checkouted in encoding UTF-16LE(UCS-2) with BOM. Before run `git clone`, you need to add following script to `%USERPROFILE%\.gitconfig` . 240 | 241 | ```ini 242 | [filter "utf16"] 243 | clean = iconv -f utf-16le -t utf-8 244 | smudge = iconv -f utf-8 -t utf-16le 245 | required 246 | ``` 247 | 248 | # Localization 249 | 250 | See this file : [CommandTrayHost/CommandTrayHost/language_data.h](https://github.com/rexdf/CommandTrayHost/blob/master/CommandTrayHost/language_data.h) 251 | 252 | # Help wanted 253 | 254 | - [ ] When restart process, keep the history standard output and standard error output in a ConsoleHelper. That's why there is a `use_builtin_console`. Maybe I have to inject some code to child process. [ConEmu](https://github.com/Maximus5/ConEmu) 255 | 256 | - [ ] [Elevated Startup](https://stefansundin.github.io/elevatedstartup/) 257 | 258 | - [ ] UIPI (User Interface Privilege Isolation) Bypass. `ChangeWindowMessageFilterEx` 259 | 260 | 261 | # Thanks 262 | 263 | [phuslu/taskbar](https://github.com/phuslu/taskbar) 264 | [@lirener](https://github.com/lirener) 265 | 266 | 267 | [i1]: https://github.com/rexdf/CommandTrayHost/issues/1 268 | 269 | -------------------------------------------------------------------------------- /CommandTrayHost/updater.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "updater.h" 3 | #include "utils.hpp" 4 | #include "language.h" 5 | #include "CommandTrayHost.h" 6 | 7 | 8 | #ifdef _WIN64 9 | #define FOLDER_NAME L"CommandTrayHost-x64" 10 | #else 11 | 12 | #if VER_PRODUCTBUILD == 7600 13 | #define FOLDER_NAME L"CommandTrayHost-xp-x86" 14 | #else 15 | #define FOLDER_NAME L"CommandTrayHost-x86" 16 | #endif 17 | 18 | #endif 19 | 20 | extern int volatile atom_variable_for_updater; 21 | //extern bool auto_update; 22 | extern bool skip_prerelease; 23 | extern bool keep_update_history; 24 | extern BOOL isZHCN; 25 | 26 | bool unzip(BSTR source, BSTR dest) 27 | { 28 | //https://www.codeproject.com/Articles/280650/Zip-Unzip-using-Windows-Shell 29 | //BSTR source = L"C:\\test.zip\\\0\0"; 30 | //BSTR dest = L"C:\\test\\\0\0"; // Currently it is assumed that the there exist Folder "Test" in C: 31 | 32 | HRESULT hResult; 33 | IShellDispatch *pISD; 34 | Folder *pToFolder = NULL; 35 | VARIANT vDir, vFile, vOpt; 36 | 37 | bool ret; 38 | 39 | hResult = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_INPROC_SERVER, IID_IShellDispatch, (void **)&pISD); 40 | 41 | if (SUCCEEDED(hResult)) 42 | { 43 | VariantInit(&vDir); 44 | vDir.vt = VT_BSTR; 45 | vDir.bstrVal = dest;//L"C:\\test.zip\\\0\0"; 46 | hResult = pISD->NameSpace(vDir, &pToFolder); 47 | 48 | if (SUCCEEDED(hResult)) 49 | { 50 | Folder *pFromFolder = NULL; 51 | 52 | VariantInit(&vFile); 53 | vFile.vt = VT_BSTR; 54 | vFile.bstrVal = source;//L"C:\\test.txt"; 55 | pISD->NameSpace(vFile, &pFromFolder); 56 | FolderItems *fi = NULL; 57 | pFromFolder->Items(&fi); 58 | VariantInit(&vOpt); 59 | vOpt.vt = VT_I4; 60 | vOpt.lVal = FOF_NO_UI;//4; // Do not display a progress dialog box 61 | 62 | // Creating a new Variant with pointer to FolderItems to be copied 63 | VARIANT newV; 64 | VariantInit(&newV); 65 | newV.vt = VT_DISPATCH; 66 | newV.pdispVal = fi; 67 | hResult = pToFolder->CopyHere(newV, vOpt); 68 | Sleep(1000); 69 | pFromFolder->Release(); 70 | pToFolder->Release(); 71 | ret = true; 72 | } 73 | else 74 | { 75 | ret = false; 76 | } 77 | pISD->Release(); 78 | 79 | } 80 | else 81 | { 82 | ret = false; 83 | } 84 | return ret; 85 | } 86 | 87 | //https://stackoverflow.com/questions/44027725/urldownloadtofile-to-memory 88 | struct ComInit 89 | { 90 | HRESULT hr; 91 | int ret; 92 | int last_atom_updater; 93 | //char * _buffer; 94 | ComInit(/*char* &buffer,const int size_of_buffer*/) : hr(::CoInitialize(nullptr)), ret(0), last_atom_updater(atom_variable_for_updater) 95 | { 96 | atom_variable_for_updater = 1; 97 | //buffer = new char[size_of_buffer]; 98 | //_buffer = buffer; 99 | LOGMESSAGE(L"CoInitialize\n"); 100 | } 101 | void SetRet(const int _ret) { ret = _ret; } 102 | ~ComInit() 103 | { 104 | if (SUCCEEDED(hr)) ::CoUninitialize(); 105 | if (0 == ret) 106 | { 107 | /*if (last_atom_updater) 108 | { 109 | msg_prompt(L"Updated!", L"Updater"); 110 | }*/ 111 | int result = msg_prompt( 112 | isZHCN ? L"更新已经完成,是否现在就启动新版本?也可以之后手动重启\n\n" : 113 | utf8_to_wstring(translate("Update done! Restart CommandTrayHost Now?\n\n")).c_str(), 114 | isZHCN ? L"更新完成" : L"Update finished", 115 | MB_YESNO); 116 | if (result == IDYES) 117 | { 118 | extern HWND hWnd; 119 | PostMessage(hWnd, WM_COMMAND, WM_TASKBARNOTIFY_MENUITEM_FORCE_RESTART, NULL); 120 | } 121 | } 122 | else if (-3 == ret) 123 | { 124 | // user click No button, do nothing and quit 125 | } 126 | else if (10 == ret) 127 | { 128 | // filename is not CommandTrayHost.exe 129 | } 130 | #if VER_PRODUCTBUILD == 7600 131 | else if (99 == ret) 132 | { 133 | // Windows XP 134 | } 135 | #endif 136 | else if (ret < 0) 137 | { 138 | if (last_atom_updater) 139 | { 140 | msg_prompt(L"There is no new version available", L"No updates"); 141 | } 142 | } 143 | else 144 | { 145 | if (last_atom_updater) 146 | { 147 | msg_prompt((L"Updater error code: " + std::to_wstring(ret)).c_str(), L"Update error!"); 148 | } 149 | } 150 | atom_variable_for_updater = -1; 151 | //delete[] _buffer; 152 | LOGMESSAGE(L"CoUninitialize\n"); 153 | } 154 | }; 155 | 156 | bool is_new_version(const std::wstring& tag_name) 157 | { 158 | PCWSTR tag_version = wcsrchr(tag_name.c_str(), L'b'), current_version = wcsrchr(VERSION_NUMS, L'b'); 159 | if (tag_version && current_version) 160 | { 161 | //int remote_version = std::stoi(tag_version), local_version = std::stoi(current_version); 162 | return std::stoi(tag_version + 1) > std::stoi(current_version + 1); 163 | } 164 | #ifdef _DEBUG 165 | else 166 | { 167 | return true; 168 | } 169 | #endif 170 | return false; 171 | } 172 | 173 | DWORD WINAPI CheckGithub(LPVOID lpParam) 174 | { 175 | //char* buffer = nullptr; 176 | //const int size_of_buffer = 1024 * 1024 * 2; 177 | 178 | ComInit init/*(buffer, size_of_buffer)*/; 179 | 180 | //if (nullptr == buffer)return -5; 181 | 182 | DWORD ret = 0; 183 | const int max_try_times = 5; 184 | nlohmann::json js; 185 | 186 | for (int i = 0; i < max_try_times; i++) 187 | { 188 | if (i)Sleep(15000); 189 | CComPtr pStream; 190 | // Open the HTTP request. 191 | HRESULT hr = URLOpenBlockingStreamW(nullptr, reinterpret_cast(lpParam), &pStream, 0, nullptr); 192 | if (FAILED(hr)) 193 | { 194 | LOGMESSAGE(L"ERROR: Could not connect. HRESULT: 0x%x\n", hr); 195 | ret = 1; 196 | init.SetRet(1); 197 | continue; 198 | } 199 | 200 | // Download the response and write it to stdout. 201 | char buffer[4096]; 202 | std::string js_str; 203 | //DWORD idx = 0; 204 | //ZeroMemory(buffer, ARRAYSIZE(buffer)); 205 | do 206 | { 207 | DWORD bytesRead = 0; 208 | hr = pStream->Read(buffer, sizeof(buffer) - 1, &bytesRead); 209 | //hr = pStream->Read(buffer + idx, size_of_buffer - idx, &bytesRead); 210 | 211 | if (bytesRead > 0) 212 | { 213 | #ifdef _DEBUG 214 | buffer[bytesRead] = 0; 215 | LOGMESSAGE(L"bytesRead > %d: %S", bytesRead, buffer); 216 | #endif 217 | js_str.append(buffer, bytesRead); 218 | //idx += bytesRead; 219 | } 220 | } while (SUCCEEDED(hr) && hr != S_FALSE); 221 | //buffer[idx] = 0; 222 | 223 | if (FAILED(hr)) 224 | { 225 | LOGMESSAGE(L"ERROR: Download failed. HRESULT: 0x%x\n", hr); 226 | ret = 2; 227 | init.SetRet(2); 228 | continue; 229 | } 230 | else 231 | { 232 | ret = 0; 233 | init.SetRet(0); 234 | } 235 | 236 | try 237 | { 238 | js = nlohmann::json::parse(js_str); 239 | assert(ret == 0); 240 | } 241 | catch (...) 242 | { 243 | ret = 3; 244 | init.SetRet(3); 245 | continue; 246 | } 247 | break; 248 | } 249 | 250 | if (ret)return ret; //failed when download json data 251 | 252 | if (js.is_structured() && js.is_array()) 253 | { 254 | for (auto&j : js) 255 | { 256 | if (j.is_structured() && j.is_object()) 257 | { 258 | std::wstring tag_name, browser_download_url, assets_name, body; 259 | bool prerelease = false; 260 | try 261 | { 262 | tag_name = utf8_to_wstring(j.at("tag_name").get()); 263 | browser_download_url = utf8_to_wstring(j.at("assets").at(0).at("browser_download_url").get()); 264 | assets_name = utf8_to_wstring(j.at("assets").at(0).at("name").get()); 265 | prerelease = j.at("prerelease"); 266 | std::string body_A = j.at("body").get(); 267 | auto pos_st = body_A.find(isZHCN ? "<--zh-CN-->" : "<--en-US-->"); 268 | auto pos_ed = body_A.find(isZHCN ? "<++zh-CN++>" : "<++en-US++>"); 269 | if (pos_st != std::string::npos && pos_ed != std::string::npos) 270 | { 271 | int offset = ARRAYSIZE("<--zh-CN-->") + 1; 272 | body_A = body_A.substr(pos_st + offset, pos_ed - pos_st - offset); 273 | } 274 | body = utf8_to_wstring(body_A); 275 | } 276 | catch (...) 277 | { 278 | msg_prompt(L"github json parse error", L"update error"); 279 | ret = 5; 280 | init.SetRet(5); 281 | break; 282 | } 283 | LOGMESSAGE("%s %s %s %d", 284 | tag_name.c_str(), 285 | assets_name.c_str(), 286 | browser_download_url.c_str(), 287 | prerelease 288 | ); 289 | if (!is_new_version(tag_name)) 290 | { 291 | ret = -1; 292 | init.SetRet(-1); 293 | break; 294 | } 295 | 296 | if (prerelease && skip_prerelease) 297 | { 298 | ret = -2; 299 | init.SetRet(-2); 300 | continue; 301 | } 302 | #if VER_PRODUCTBUILD == 7600 303 | init.SetRet(99); 304 | msg_prompt( 305 | (L"New version found!\n\nWindows XP cannot atomically download https from github, you need to do it by yourself.\n\n" + body).c_str(), 306 | tag_name.c_str(), 307 | MB_ICONINFORMATION); 308 | return 99; 309 | #endif 310 | int result = msg_prompt( 311 | isZHCN ? (L"发现新版本! 是否要下载?\n\n" + body).c_str() : 312 | (utf8_to_wstring(translate("New version found! Download?\n\n")) + body).c_str(), 313 | tag_name.c_str(), 314 | MB_YESNO); 315 | if (result == IDNO) { 316 | init.SetRet(-3); 317 | return -3; 318 | } 319 | if (!keep_update_history && PathIsDirectory(UPDATE_TEMP_DIR)) 320 | { 321 | //RemoveDirectory(UPDATE_TEMP_DIR); 322 | _wsystem(L"rd /s /q " UPDATE_TEMP_DIR); 323 | } 324 | if (FALSE == PathIsDirectory(UPDATE_TEMP_DIR)) 325 | { 326 | CreateDirectory(UPDATE_TEMP_DIR, NULL); 327 | } 328 | if (FALSE == PathIsDirectory(UPDATE_TEMP_DIR) || 329 | (!keep_update_history && PathIsDirectoryEmpty(UPDATE_TEMP_DIR) == FALSE)) 330 | { 331 | msg_prompt(L"cannot create " UPDATE_TEMP_DIR, L"updater error!", MB_ICONERROR); 332 | } 333 | for (int k = 0; k < max_try_times; k++) 334 | { 335 | if (k)Sleep(5000); 336 | HRESULT hr; 337 | //LPCTSTR Url = _T("https://api.github.com/repos/rexdf/CommandTrayHost/releases/latest"), File = _T("latest_commandtrayhost.json"); 338 | //LPCTSTR Url = L"http://127.0.0.1:5000", File = L"test.txt"; 339 | hr = URLDownloadToFile(0, 340 | (browser_download_url).c_str(), 341 | (L"temp\\" + assets_name).c_str(), 342 | 0, 0); 343 | switch (hr) 344 | { 345 | case S_OK: 346 | LOGMESSAGE(L"Successful download\n"); 347 | ret = 0; 348 | init.SetRet(0); 349 | break; 350 | case E_OUTOFMEMORY: 351 | LOGMESSAGE(L"Out of memory error\n"); 352 | ret = 6; 353 | init.SetRet(6); 354 | continue; 355 | break; 356 | case INET_E_DOWNLOAD_FAILURE: 357 | LOGMESSAGE(L"Cannot access server data\n"); 358 | ret = 7; 359 | init.SetRet(7); 360 | continue; 361 | break; 362 | default: 363 | LOGMESSAGE(L"Unknown error\n"); 364 | ret = 8; 365 | init.SetRet(8); 366 | continue; 367 | break; 368 | } 369 | if (0 == ret)break; 370 | } 371 | if (ret == 0) 372 | { 373 | extern TCHAR szPathToExeDir[MAX_PATH * 10]; 374 | using namespace std::string_literals; 375 | BSTR zipfilename = SysAllocString((szPathToExeDir + (L"\\" UPDATE_TEMP_DIR L"\\" + assets_name) + L"\\").c_str()); 376 | BSTR dst_dir = SysAllocString((szPathToExeDir + L"\\" UPDATE_TEMP_DIR L"\\"s).c_str()); 377 | if (unzip(zipfilename, dst_dir)) 378 | { 379 | LOGMESSAGE(L"sucess download and unzip!"); 380 | ret = 0; 381 | init.SetRet(0); 382 | } 383 | else 384 | { 385 | ret = 9; 386 | init.SetRet(9); 387 | //continue; 388 | } 389 | SysFreeString(zipfilename); 390 | SysFreeString(dst_dir); 391 | if (ret == 0) 392 | { 393 | extern TCHAR szPathToExe[MAX_PATH * 10]; 394 | PCWSTR exe_name_pointer = wcsrchr(szPathToExe, L'\\'); 395 | LOGMESSAGE(L"exe_name_pointer: %s\n", exe_name_pointer); 396 | if (StrCmp(exe_name_pointer + 1, L"CommandTrayHost.exe") == 0) 397 | { 398 | if (TRUE == PathFileExists(UPDATE_TEMP_DIR L"\\CommandTrayHost.exe")) 399 | { 400 | DeleteFile(UPDATE_TEMP_DIR L"\\CommandTrayHost-" VERSION_NUMS L".exe"); 401 | } 402 | MoveFile(L"CommandTrayHost.exe", UPDATE_TEMP_DIR L"\\CommandTrayHost-" VERSION_NUMS L".exe"); 403 | MoveFile(UPDATE_TEMP_DIR L"\\" FOLDER_NAME L"\\CommandTrayHost.exe", L"CommandTrayHost.exe"); 404 | } 405 | else 406 | { 407 | msg_prompt("Update download and unzip done.\n\n" 408 | L"Filename is not CommandTrayHost.exe," 409 | L" you need to copy and rename by hand", 410 | L"update error", 411 | MB_ICONERROR); 412 | ret = 10; 413 | init.SetRet(10); 414 | break; 415 | } 416 | if (ret == 0)break; 417 | } 418 | } 419 | } 420 | else 421 | { 422 | ret = 4; 423 | init.SetRet(4); 424 | } 425 | if (ret == 0)break; 426 | } 427 | } 428 | return ret; 429 | } 430 | 431 | BOOL UpdaterChecker(PWSTR lpUrl, HANDLE &hThread) 432 | { 433 | 434 | static int volatile atom_variable_for_msg = 0; 435 | if (atom_variable_for_updater > 0) 436 | { 437 | if (atom_variable_for_msg == 0) { 438 | atom_variable_for_msg = 1; 439 | msg_prompt(L"Updater is already running!", L"Updater is running"); 440 | atom_variable_for_msg = 0; 441 | } 442 | return TRUE; 443 | } 444 | hThread = CreateThread(NULL, // default security attributes 445 | 0, // use default stack size 446 | (LPTHREAD_START_ROUTINE)CheckGithub, // thread function 447 | reinterpret_cast(lpUrl), // no thread function argument 448 | 0, // use default creation flags 449 | NULL); 450 | if (hThread == NULL) 451 | { 452 | return FALSE; 453 | } 454 | return TRUE; 455 | } 456 | 457 | -------------------------------------------------------------------------------- /third_party/ccronexpr/ccronexpr_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015, alex at staticlibs.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * File: CronExprParser_test.cpp 19 | * Author: alex 20 | * 21 | * Created on February 24, 2015, 9:36 AM 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "ccronexpr.h" 31 | 32 | #define MAX_SECONDS 60 33 | #define CRON_MAX_MINUTES 60 34 | #define CRON_MAX_HOURS 24 35 | #define CRON_MAX_DAYS_OF_WEEK 8 36 | #define CRON_MAX_DAYS_OF_MONTH 32 37 | #define CRON_MAX_MONTHS 12 38 | 39 | #define INVALID_INSTANT ((time_t) -1) 40 | 41 | #define DATE_FORMAT "%Y-%m-%d_%H:%M:%S" 42 | 43 | #ifdef CRON_TEST_MALLOC 44 | static int cronAllocations = 0; 45 | static int cronTotalAllocations = 0; 46 | static int maxAlloc = 0; 47 | void* cronMalloc(size_t n) { 48 | cronAllocations++; 49 | cronTotalAllocations++; 50 | if (cronAllocations > maxAlloc) { 51 | maxAlloc = cronAllocations; 52 | } 53 | return malloc(n); 54 | } 55 | 56 | void cronFree(void* p) { 57 | cronAllocations--; 58 | free(p); 59 | } 60 | #endif 61 | 62 | #ifndef ANDROID 63 | #ifndef _WIN32 64 | time_t timegm(struct tm* __tp); 65 | #else /* _WIN32 */ 66 | static time_t timegm(struct tm* tm) { 67 | return _mkgmtime(tm); 68 | } 69 | #endif /* _WIN32 */ 70 | #else /* ANDROID */ 71 | static time_t timegm(struct tm * const t) { 72 | /* time_t is signed on Android. */ 73 | static const time_t kTimeMax = ~(1L << (sizeof (time_t) * CHAR_BIT - 1)); 74 | static const time_t kTimeMin = (1L << (sizeof (time_t) * CHAR_BIT - 1)); 75 | time64_t result = timegm64(t); 76 | if (result < kTimeMin || result > kTimeMax) 77 | return -1; 78 | return result; 79 | } 80 | #endif 81 | 82 | /** 83 | * uint8_t* replace char* for storing hit dates, set_bit and get_bit are used as handlers 84 | */ 85 | uint8_t cron_get_bit(uint8_t* rbyte, int idx); 86 | void cron_set_bit(uint8_t* rbyte, int idx); 87 | void cron_del_bit(uint8_t* rbyte, int idx); 88 | 89 | static int crons_equal(cron_expr* cr1, cron_expr* cr2) { 90 | unsigned int i; 91 | for (i = 0; i < ARRAY_LEN(cr1->seconds); i++) { 92 | if (cr1->seconds[i] != cr2->seconds[i]) { 93 | printf("seconds not equal @%d %02x != %02x", i, cr1->seconds[i], cr2->seconds[i]); 94 | return 0; 95 | } 96 | } 97 | for (i = 0; i < ARRAY_LEN(cr1->minutes); i++) { 98 | if (cr1->minutes[i] != cr2->minutes[i]) { 99 | printf("minutes not equal @%d %02x != %02x", i, cr1->minutes[i], cr2->minutes[i]); 100 | return 0; 101 | } 102 | } 103 | for (i = 0; i < ARRAY_LEN(cr1->hours); i++) { 104 | if (cr1->hours[i] != cr2->hours[i]) { 105 | printf("hours not equal @%d %02x != %02x", i, cr1->hours[i], cr2->hours[i]); 106 | return 0; 107 | } 108 | } 109 | for (i = 0; i < ARRAY_LEN(cr1->days_of_week); i++) { 110 | if (cr1->days_of_week[i] != cr2->days_of_week[i]) { 111 | printf("days_of_week not equal @%d %02x != %02x", i, cr1->days_of_week[i], cr2->days_of_week[i]); 112 | return 0; 113 | } 114 | } 115 | for (i = 0; i < ARRAY_LEN(cr1->days_of_month); i++) { 116 | if (cr1->days_of_month[i] != cr2->days_of_month[i]) { 117 | printf("days_of_month not equal @%d %02x != %02x", i, cr1->days_of_month[i], cr2->days_of_month[i]); 118 | return 0; 119 | } 120 | } 121 | for (i = 0; i < ARRAY_LEN(cr1->months); i++) { 122 | if (cr1->months[i] != cr2->months[i]) { 123 | printf("months not equal @%d %02x != %02x", i, cr1->months[i], cr2->months[i]); 124 | return 0; 125 | } 126 | } 127 | return 1; 128 | } 129 | 130 | int one_dec_num(const char ch) { 131 | switch (ch) { 132 | case '0': 133 | return 0; 134 | case '1': 135 | return 1; 136 | case '2': 137 | return 2; 138 | case '3': 139 | return 3; 140 | case '4': 141 | return 4; 142 | case '5': 143 | return 5; 144 | case '6': 145 | return 6; 146 | case '7': 147 | return 7; 148 | case '8': 149 | return 8; 150 | case '9': 151 | return 9; 152 | default: 153 | return -1; 154 | } 155 | } 156 | 157 | int two_dec_num(const char* first) { 158 | return one_dec_num(first[0]) * 10 + one_dec_num(first[1]); 159 | } 160 | 161 | /* strptime is not available in msvc */ 162 | /* 2012-07-01_09:53:50 */ 163 | /* 0123456789012345678 */ 164 | struct tm* poors_mans_strptime(const char* str) { 165 | struct tm* cal = (struct tm*) malloc(sizeof(struct tm)); 166 | switch (str[3]) { 167 | case '7': 168 | cal->tm_year = 107; 169 | break; 170 | case '8': 171 | cal->tm_year = 108; 172 | break; 173 | case '9': 174 | cal->tm_year = 109; 175 | break; 176 | case '0': 177 | cal->tm_year = 110; 178 | break; 179 | case '1': 180 | cal->tm_year = 111; 181 | break; 182 | case '2': 183 | cal->tm_year = 112; 184 | break; 185 | } 186 | cal->tm_mon = two_dec_num(str + 5) - 1; 187 | cal->tm_mday = two_dec_num(str + 8); 188 | cal->tm_wday = 0; 189 | cal->tm_yday = 0; 190 | cal->tm_hour = two_dec_num(str + 11); 191 | cal->tm_min = two_dec_num(str + 14); 192 | cal->tm_sec = two_dec_num(str + 17); 193 | return cal; 194 | } 195 | 196 | void check_next(const char* pattern, const char* initial, const char* expected) { 197 | const char* err = NULL; 198 | cron_expr parsed; 199 | memset(&parsed, 0, sizeof(parsed)); 200 | cron_parse_expr(pattern, &parsed, &err); 201 | 202 | struct tm* calinit = poors_mans_strptime(initial); 203 | time_t dateinit = timegm(calinit); 204 | assert(-1 != dateinit); 205 | time_t datenext = cron_next(&parsed, dateinit); 206 | struct tm* calnext = gmtime(&datenext); 207 | assert(calnext); 208 | char* buffer = (char*) malloc(21); 209 | memset(buffer, 0, 21); 210 | strftime(buffer, 20, DATE_FORMAT, calnext); 211 | if (0 != strcmp(expected, buffer)) { 212 | printf("Pattern: %s\n", pattern); 213 | printf("Initial: %s\n", initial); 214 | printf("Expected: %s\n", expected); 215 | printf("Actual: %s\n", buffer); 216 | assert(0); 217 | } 218 | free(buffer); 219 | free(calinit); 220 | } 221 | 222 | void check_same(const char* expr1, const char* expr2) { 223 | cron_expr parsed1; 224 | memset(&parsed1, 0, sizeof(parsed1)); 225 | cron_parse_expr(expr1, &parsed1, NULL); 226 | cron_expr parsed2; 227 | memset(&parsed2, 0, sizeof(parsed2)); 228 | cron_parse_expr(expr2, &parsed2, NULL); 229 | assert(crons_equal(&parsed1, &parsed2)); 230 | } 231 | 232 | void check_calc_invalid() { 233 | cron_expr parsed; 234 | memset(&parsed, 0, sizeof(parsed)); 235 | cron_parse_expr("0 0 0 31 6 *", &parsed, NULL); 236 | struct tm * calinit = poors_mans_strptime("2012-07-01_09:53:50"); 237 | time_t dateinit = timegm(calinit); 238 | time_t res = cron_next(&parsed, dateinit); 239 | assert(INVALID_INSTANT == res); 240 | free(calinit); 241 | } 242 | 243 | void check_expr_invalid(const char* expr) { 244 | const char* err = NULL; 245 | cron_expr test; 246 | memset(&test, 0, sizeof(test)); 247 | cron_parse_expr(expr, &test, &err); 248 | assert(err); 249 | } 250 | 251 | void test_expr() { 252 | check_next("*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-02_01:00:00"); 253 | check_next("*/15 * 1-4 * * *", "2012-07-01_09:53:00", "2012-07-02_01:00:00"); 254 | check_next("0 */2 1-4 * * *", "2012-07-01_09:00:00", "2012-07-02_01:00:00"); 255 | check_next("* * * * * *", "2012-07-01_09:00:00", "2012-07-01_09:00:01"); 256 | check_next("* * * * * *", "2012-12-01_09:00:58", "2012-12-01_09:00:59"); 257 | check_next("10 * * * * *", "2012-12-01_09:42:09", "2012-12-01_09:42:10"); 258 | check_next("11 * * * * *", "2012-12-01_09:42:10", "2012-12-01_09:42:11"); 259 | check_next("10 * * * * *", "2012-12-01_09:42:10", "2012-12-01_09:43:10"); 260 | check_next("10-15 * * * * *", "2012-12-01_09:42:09", "2012-12-01_09:42:10"); 261 | check_next("10-15 * * * * *", "2012-12-01_21:42:14", "2012-12-01_21:42:15"); 262 | check_next("0 * * * * *", "2012-12-01_21:10:42", "2012-12-01_21:11:00"); 263 | check_next("0 * * * * *", "2012-12-01_21:11:00", "2012-12-01_21:12:00"); 264 | check_next("0 11 * * * *", "2012-12-01_21:10:42", "2012-12-01_21:11:00"); 265 | check_next("0 10 * * * *", "2012-12-01_21:11:00", "2012-12-01_22:10:00"); 266 | check_next("0 0 * * * *", "2012-09-30_11:01:00", "2012-09-30_12:00:00"); 267 | check_next("0 0 * * * *", "2012-09-30_12:00:00", "2012-09-30_13:00:00"); 268 | check_next("0 0 * * * *", "2012-09-10_23:01:00", "2012-09-11_00:00:00"); 269 | check_next("0 0 * * * *", "2012-09-11_00:00:00", "2012-09-11_01:00:00"); 270 | check_next("0 0 0 * * *", "2012-09-01_14:42:43", "2012-09-02_00:00:00"); 271 | check_next("0 0 0 * * *", "2012-09-02_00:00:00", "2012-09-03_00:00:00"); 272 | check_next("* * * 10 * *", "2012-10-09_15:12:42", "2012-10-10_00:00:00"); 273 | check_next("* * * 10 * *", "2012-10-11_15:12:42", "2012-11-10_00:00:00"); 274 | check_next("0 0 0 * * *", "2012-09-30_15:12:42", "2012-10-01_00:00:00"); 275 | check_next("0 0 0 * * *", "2012-10-01_00:00:00", "2012-10-02_00:00:00"); 276 | check_next("0 0 0 * * *", "2012-08-30_15:12:42", "2012-08-31_00:00:00"); 277 | check_next("0 0 0 * * *", "2012-08-31_00:00:00", "2012-09-01_00:00:00"); 278 | check_next("0 0 0 * * *", "2012-10-30_15:12:42", "2012-10-31_00:00:00"); 279 | check_next("0 0 0 * * *", "2012-10-31_00:00:00", "2012-11-01_00:00:00"); 280 | check_next("0 0 0 1 * *", "2012-10-30_15:12:42", "2012-11-01_00:00:00"); 281 | check_next("0 0 0 1 * *", "2012-11-01_00:00:00", "2012-12-01_00:00:00"); 282 | check_next("0 0 0 1 * *", "2010-12-31_15:12:42", "2011-01-01_00:00:00"); 283 | check_next("0 0 0 1 * *", "2011-01-01_00:00:00", "2011-02-01_00:00:00"); 284 | check_next("0 0 0 31 * *", "2011-10-30_15:12:42", "2011-10-31_00:00:00"); 285 | check_next("0 0 0 1 * *", "2011-10-30_15:12:42", "2011-11-01_00:00:00"); 286 | check_next("* * * * * 2", "2010-10-25_15:12:42", "2010-10-26_00:00:00"); 287 | check_next("* * * * * 2", "2010-10-20_15:12:42", "2010-10-26_00:00:00"); 288 | check_next("* * * * * 2", "2010-10-27_15:12:42", "2010-11-02_00:00:00"); 289 | check_next("55 5 * * * *", "2010-10-27_15:04:54", "2010-10-27_15:05:55"); 290 | check_next("55 5 * * * *", "2010-10-27_15:05:55", "2010-10-27_16:05:55"); 291 | check_next("55 * 10 * * *", "2010-10-27_09:04:54", "2010-10-27_10:00:55"); 292 | check_next("55 * 10 * * *", "2010-10-27_10:00:55", "2010-10-27_10:01:55"); 293 | check_next("* 5 10 * * *", "2010-10-27_09:04:55", "2010-10-27_10:05:00"); 294 | check_next("* 5 10 * * *", "2010-10-27_10:05:00", "2010-10-27_10:05:01"); 295 | check_next("55 * * 3 * *", "2010-10-02_10:05:54", "2010-10-03_00:00:55"); 296 | check_next("55 * * 3 * *", "2010-10-03_00:00:55", "2010-10-03_00:01:55"); 297 | check_next("* * * 3 11 *", "2010-10-02_14:42:55", "2010-11-03_00:00:00"); 298 | check_next("* * * 3 11 *", "2010-11-03_00:00:00", "2010-11-03_00:00:01"); 299 | check_next("0 0 0 29 2 *", "2007-02-10_14:42:55", "2008-02-29_00:00:00"); 300 | check_next("0 0 0 29 2 *", "2008-02-29_00:00:00", "2012-02-29_00:00:00"); 301 | check_next("0 0 7 ? * MON-FRI", "2009-09-26_00:42:55", "2009-09-28_07:00:00"); 302 | check_next("0 0 7 ? * MON-FRI", "2009-09-28_07:00:00", "2009-09-29_07:00:00"); 303 | check_next("0 30 23 30 1/3 ?", "2010-12-30_00:00:00", "2011-01-30_23:30:00"); 304 | check_next("0 30 23 30 1/3 ?", "2011-01-30_23:30:00", "2011-04-30_23:30:00"); 305 | check_next("0 30 23 30 1/3 ?", "2011-04-30_23:30:00", "2011-07-30_23:30:00"); 306 | } 307 | 308 | void test_parse() { 309 | 310 | check_same("* * * 2 * *", "* * * 2 * ?"); 311 | check_same("57,59 * * * * *", "57/2 * * * * *"); 312 | check_same("1,3,5 * * * * *", "1-6/2 * * * * *"); 313 | check_same("* * 4,8,12,16,20 * * *", "* * 4/4 * * *"); 314 | check_same("* * * * * 0-6", "* * * * * TUE,WED,THU,FRI,SAT,SUN,MON"); 315 | check_same("* * * * * 0", "* * * * * SUN"); 316 | check_same("* * * * * 0", "* * * * * 7"); 317 | check_same("* * * * 1-12 *", "* * * * FEB,JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC *"); 318 | check_same("* * * * 2 *", "* * * * Feb *"); 319 | check_same("* * * * 1 *", "* * * * 1 *"); 320 | 321 | check_expr_invalid("77 * * * * *"); 322 | check_expr_invalid("44-77 * * * * *"); 323 | check_expr_invalid("* 77 * * * *"); 324 | check_expr_invalid("* 44-77 * * * *"); 325 | check_expr_invalid("* * 27 * * *"); 326 | check_expr_invalid("* * 23-28 * * *"); 327 | check_expr_invalid("* * * 45 * *"); 328 | check_expr_invalid("* * * 28-45 * *"); 329 | check_expr_invalid("0 0 0 25 13 ?"); 330 | check_expr_invalid("0 0 0 25 0 ?"); 331 | check_expr_invalid("0 0 0 32 12 ?"); 332 | check_expr_invalid("* * * * 11-13 *"); 333 | } 334 | 335 | void test_bits() { 336 | 337 | uint8_t testbyte[8]; 338 | memset(testbyte, 0, 8); 339 | int err = 0; 340 | int i; 341 | 342 | for (i = 0; i <= 63; i++) { 343 | cron_set_bit(testbyte, i); 344 | if (!cron_get_bit(testbyte, i)) { 345 | printf("Bit set error! Bit: %d!\n", i); 346 | err = 1; 347 | } 348 | cron_del_bit(testbyte, i); 349 | if (cron_get_bit(testbyte, i)) { 350 | printf("Bit clear error! Bit: %d!\n", i); 351 | err = 1; 352 | } 353 | assert(!err); 354 | } 355 | 356 | for (i = 0; i < 12; i++) { 357 | cron_set_bit(testbyte, i); 358 | } 359 | if (testbyte[0] != 0xff) { 360 | err = 1; 361 | } 362 | if (testbyte[1] != 0x0f) { 363 | err = 1; 364 | } 365 | 366 | assert(!err); 367 | } 368 | 369 | /* For this test to work you need to set "-DCRON_TEST_MALLOC=1"*/ 370 | #ifdef CRON_TEST_MALLOC 371 | void test_memory() { 372 | cron_expr cron; 373 | const char* err; 374 | 375 | cron_parse_expr("* * * * * *", &cron, &err); 376 | if (cronAllocations != 0) { 377 | printf("Allocations != 0 but %d", cronAllocations); 378 | assert(0); 379 | } 380 | printf("Allocations: total: %d, max: %d", cronTotalAllocations, maxAlloc); 381 | } 382 | #endif 383 | 384 | int main() { 385 | 386 | test_bits(); 387 | 388 | test_expr(); 389 | test_parse(); 390 | check_calc_invalid(); 391 | #ifdef CRON_TEST_MALLOC 392 | test_memory(); /* For this test to work you need to set "-DCRON_TEST_MALLOC=1"*/ 393 | #endif 394 | printf("\nAll OK!"); 395 | return 0; 396 | } 397 | 398 | -------------------------------------------------------------------------------- /CommandTrayHost/admin_singleton.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CommandTrayHost.h" 3 | #include "admin_singleton.h" 4 | #include "utils.hpp" 5 | #include "configure.h" 6 | 7 | extern nlohmann::json global_stat; 8 | 9 | extern bool is_runas_admin; 10 | extern bool is_from_self_restart; 11 | 12 | extern HANDLE ghMutex; 13 | 14 | extern TCHAR szPathToExe[MAX_PATH * 10]; 15 | extern TCHAR szPathToExeToken[MAX_PATH * 10]; 16 | extern TCHAR szPathToExeDir[MAX_PATH * 10]; 17 | 18 | // https://stackoverflow.com/questions/15913202/add-application-to-startup-registry 19 | BOOL IsMyProgramRegisteredForStartup(PCWSTR pszAppName) 20 | { 21 | HKEY hKey = NULL; 22 | LONG lResult = 0; 23 | BOOL fSuccess = TRUE; 24 | DWORD dwRegType = REG_SZ; 25 | TCHAR szPathToExe_reg[MAX_PATH * 5] = {}; 26 | DWORD dwSize = sizeof(szPathToExe_reg); 27 | 28 | lResult = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_READ, &hKey); 29 | 30 | fSuccess = (lResult == ERROR_SUCCESS); 31 | 32 | if (fSuccess) 33 | { 34 | #if VER_PRODUCTBUILD == 7600 35 | lResult = RegQueryValueEx(hKey, pszAppName, NULL, &dwRegType, (LPBYTE)&szPathToExe_reg, &dwSize); 36 | #else 37 | lResult = RegGetValue(hKey, NULL, pszAppName, RRF_RT_REG_SZ, &dwRegType, szPathToExe_reg, &dwSize); 38 | #endif 39 | fSuccess = (lResult == ERROR_SUCCESS); 40 | } 41 | 42 | if (fSuccess) 43 | { 44 | *wcsrchr(szPathToExe_reg, L'"') = 0; 45 | /*size_t len = 0; 46 | if (SUCCEEDED(StringCchLength(szPathToExe_reg, ARRAYSIZE(szPathToExe_reg), &len))) 47 | { 48 | LOGMESSAGE(L"[%c] [%c] \n", szPathToExe_reg[len - 1], szPathToExe_reg[len - 2]); 49 | szPathToExe_reg[len - 2] = 0; // There is a space at end except for quote ". 50 | }*/ 51 | fSuccess = (wcscmp(szPathToExe, szPathToExe_reg + 1) == 0) ? TRUE : FALSE; 52 | //fSuccess = (wcslen(szPathToExe) > 0) ? TRUE : FALSE; 53 | LOGMESSAGE(L"\n szPathToExe_reg: %s\n szPathToExe : %s \nfSuccess:%d \n", szPathToExe_reg + 1, szPathToExe, fSuccess); 54 | 55 | } 56 | 57 | if (hKey != NULL) 58 | { 59 | RegCloseKey(hKey); 60 | hKey = NULL; 61 | } 62 | 63 | return fSuccess; 64 | } 65 | 66 | BOOL RegisterMyProgramForStartup(PCWSTR pszAppName, PCWSTR pathToExe, PCWSTR args) 67 | { 68 | HKEY hKey = NULL; 69 | LONG lResult = 0; 70 | BOOL fSuccess = TRUE; 71 | DWORD dwSize; 72 | 73 | const size_t count = MAX_PATH * 20; 74 | TCHAR szValue[count] = {}; 75 | 76 | if (FAILED(StringCchCopy(szValue, count, L"\"")) || 77 | FAILED(StringCchCat(szValue, count, pathToExe)) || 78 | FAILED(StringCchCat(szValue, count, L"\" ")) 79 | ) 80 | { 81 | LOGMESSAGE(L"StringCchCopy failed\n"); 82 | msg_prompt(/*NULL,*/ L"RegisterMyProgramForStartup szValue Failed!", L"Error", MB_OK | MB_ICONERROR); 83 | } 84 | 85 | /*wcscpy_s(szValue, count, L"\""); 86 | wcscat_s(szValue, count, pathToExe); 87 | wcscat_s(szValue, count, L"\" ");*/ 88 | 89 | if (args != NULL) 90 | { 91 | // caller should make sure "args" is quoted if any single argument has a space 92 | // e.g. (L"-name \"Mark Voidale\""); 93 | // wcscat_s(szValue, count, args); 94 | if (FAILED(StringCchCat(szValue, count, args))) 95 | { 96 | LOGMESSAGE(L"StringCchCat failed\n"); 97 | msg_prompt(/*NULL, */L"RegisterMyProgramForStartup szValue Failed!", L"Error", MB_OK | MB_ICONERROR); 98 | } 99 | } 100 | 101 | lResult = RegCreateKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, NULL, 0, (KEY_WRITE | KEY_READ), NULL, &hKey, NULL); 102 | 103 | fSuccess = (lResult == 0); 104 | 105 | if (fSuccess) 106 | { 107 | dwSize = static_cast((wcslen(szValue) + 1) * 2); 108 | lResult = RegSetValueEx(hKey, pszAppName, 0, REG_SZ, reinterpret_cast(szValue), dwSize); 109 | fSuccess = (lResult == 0); 110 | LOGMESSAGE(L"%s %s %d %d\n", pszAppName, szValue, fSuccess, GetLastError()); 111 | } 112 | 113 | if (hKey != NULL) 114 | { 115 | RegCloseKey(hKey); 116 | hKey = NULL; 117 | } 118 | 119 | return fSuccess; 120 | } 121 | 122 | BOOL DisableStartUp2(PCWSTR valueName) 123 | { 124 | #if VER_PRODUCTBUILD == 7600 125 | HKEY hKey = NULL; 126 | if ((ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, 127 | L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 128 | 0, 129 | KEY_ALL_ACCESS, 130 | &hKey)) && 131 | (ERROR_SUCCESS == RegDeleteValue( 132 | hKey, 133 | //CommandTrayHost) 134 | valueName) 135 | ) 136 | ) 137 | { 138 | if (hKey != NULL) 139 | { 140 | RegCloseKey(hKey); 141 | hKey = NULL; 142 | } 143 | return TRUE; 144 | } 145 | #else 146 | if (ERROR_SUCCESS == RegDeleteKeyValue( 147 | HKEY_CURRENT_USER, 148 | L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 149 | //CommandTrayHost) 150 | valueName) 151 | ) 152 | { 153 | return TRUE; 154 | } 155 | #endif 156 | else 157 | { 158 | #if VER_PRODUCTBUILD == 7600 159 | if (hKey != NULL) 160 | { 161 | RegCloseKey(hKey); 162 | hKey = NULL; 163 | } 164 | #endif 165 | return FALSE; 166 | } 167 | } 168 | 169 | BOOL DisableStartUp() 170 | { 171 | #ifdef CLEANUP_HISTORY_STARTUP 172 | DisableStartUp2(CommandTrayHost); 173 | #endif 174 | return DisableStartUp2(szPathToExeToken); 175 | } 176 | 177 | BOOL EnableStartup() 178 | { 179 | #ifdef CLEANUP_HISTORY_STARTUP 180 | DisableStartUp2(CommandTrayHost); 181 | #endif 182 | //TCHAR szPathToExe[MAX_PATH * 10]; 183 | //GetModuleFileName(NULL, szPathToExe, ARRAYSIZE(szPathToExe)); 184 | //return RegisterMyProgramForStartup(CommandTrayHost, szPathToExe, L""); 185 | return RegisterMyProgramForStartup(szPathToExeToken, szPathToExe, L""); 186 | } 187 | 188 | BOOL IsRunAsAdministrator() 189 | { 190 | BOOL fIsRunAsAdmin = FALSE; 191 | DWORD dwError = ERROR_SUCCESS; 192 | PSID pAdministratorsGroup = NULL; 193 | 194 | // Allocate and initialize a SID of the administrators group. 195 | SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; 196 | if (!AllocateAndInitializeSid( 197 | &NtAuthority, 198 | 2, 199 | SECURITY_BUILTIN_DOMAIN_RID, 200 | DOMAIN_ALIAS_RID_ADMINS, 201 | 0, 0, 0, 0, 0, 0, 202 | &pAdministratorsGroup)) 203 | { 204 | dwError = GetLastError(); 205 | goto Cleanup; 206 | } 207 | 208 | // Determine whether the SID of administrators group is enabled in 209 | // the primary access token of the process. 210 | if (!CheckTokenMembership(NULL, pAdministratorsGroup, &fIsRunAsAdmin)) 211 | { 212 | dwError = GetLastError(); 213 | goto Cleanup; 214 | } 215 | 216 | Cleanup: 217 | // Centralized cleanup for all allocated resources. 218 | if (pAdministratorsGroup) 219 | { 220 | FreeSid(pAdministratorsGroup); 221 | pAdministratorsGroup = NULL; 222 | } 223 | 224 | // Throw the error if something failed in the function. 225 | if (ERROR_SUCCESS != dwError) 226 | { 227 | throw dwError; 228 | } 229 | 230 | return fIsRunAsAdmin; 231 | } 232 | 233 | /* 234 | void delete_lockfile() 235 | { 236 | if (NULL == DeleteFile(LOCK_FILE_NAME)) 237 | { 238 | LOGMESSAGE(L"Delete " LOCK_FILE_NAME " Failed! error code: %d\n", GetLastError()); 239 | } 240 | } 241 | */ 242 | 243 | void RestartNow() 244 | { 245 | if (szPathToExe[0]) 246 | { 247 | SHELLEXECUTEINFO sei = { sizeof(sei) }; 248 | //sei.lpVerb = is_runas_admin ? L"runas" : L"open"; 249 | sei.lpVerb = L"open"; 250 | //sei.lpFile = szPath; 251 | sei.lpFile = szPathToExe; 252 | sei.lpParameters = L" force-restart"; 253 | sei.hwnd = NULL; 254 | sei.nShow = SW_NORMAL; 255 | if (!ShellExecuteEx(&sei)) 256 | { 257 | DWORD dwError = GetLastError(); 258 | if (dwError == ERROR_CANCELLED) 259 | { 260 | msg_prompt(L"Restart Failed!", L"Error", MB_OK | MB_ICONERROR); 261 | } 262 | } 263 | else 264 | { 265 | extern HICON gHicon; 266 | CLEANUP_BEFORE_QUIT(-1); 267 | _exit(2); 268 | } 269 | } 270 | } 271 | 272 | void ElevateNow() 273 | { 274 | if (!is_runas_admin) 275 | { 276 | //wchar_t szPath[MAX_PATH * 10]; 277 | //if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath))) 278 | if (szPathToExe[0]) 279 | { 280 | // Launch itself as admin 281 | SHELLEXECUTEINFO sei = { sizeof(sei) }; 282 | sei.lpVerb = L"runas"; 283 | //sei.lpFile = szPath; 284 | sei.lpFile = szPathToExe; 285 | sei.hwnd = NULL; 286 | sei.nShow = SW_NORMAL; 287 | 288 | //delete_lockfile(); 289 | //CLEAN_MUTEX(); 290 | if (!ShellExecuteEx(&sei)) 291 | { 292 | DWORD dwError = GetLastError(); 293 | 294 | /*DWORD pid = GetCurrentProcessId(); 295 | 296 | std::ofstream fo(LOCK_FILE_NAME); 297 | if (fo.good()) 298 | { 299 | fo << pid; 300 | LOGMESSAGE(L"pid has wrote\n"); 301 | } 302 | fo.close(); 303 | */ 304 | 305 | if (dwError == ERROR_CANCELLED) 306 | { 307 | // The user refused to allow privileges elevation. 308 | msg_prompt(/*NULL, */L"End user did not allow elevation!", L"Error", MB_OK | MB_ICONERROR); 309 | //bool is_another_instance_running(); 310 | //is_another_instance_running(); 311 | } 312 | } 313 | else 314 | { 315 | /*delete_lockfile(); 316 | kill_all(js); 317 | DeleteTrayIcon();*/ 318 | extern HICON gHicon; 319 | //extern CRITICAL_SECTION CriticalSection; 320 | //extern bool enable_critialsection; 321 | CLEANUP_BEFORE_QUIT(1); 322 | _exit(1); // Quit itself 323 | } 324 | } 325 | } 326 | else 327 | { 328 | //Sleep(200); // Child process wait for parents to quit. 329 | } 330 | } 331 | 332 | bool check_runas_admin() 333 | { 334 | BOOL bAlreadyRunningAsAdministrator = FALSE; 335 | try 336 | { 337 | bAlreadyRunningAsAdministrator = IsRunAsAdministrator(); 338 | } 339 | catch (...) 340 | { 341 | LOGMESSAGE(L"Failed to determine if application was running with admin rights\n"); 342 | DWORD dwErrorCode = GetLastError(); 343 | LOGMESSAGE(L"Error code returned was 0x%08lx\n", dwErrorCode); 344 | } 345 | return bAlreadyRunningAsAdministrator; 346 | } 347 | 348 | /*void check_admin(bool is_admin) 349 | { 350 | bool require_admin = false; 351 | //#ifdef _DEBUG 352 | // try_read_optional_json(global_stat, require_admin, "require_admin", __FUNCTION__); 353 | //#else 354 | try_read_optional_json(global_stat, require_admin, "require_admin"); 355 | //#endif 356 | 357 | if (require_admin) 358 | { 359 | ElevateNow(); 360 | } 361 | }*/ 362 | 363 | void check_admin() 364 | { 365 | if (global_stat.value("require_admin", false)) 366 | { 367 | ElevateNow(); 368 | } 369 | } 370 | 371 | bool init_cth_path() 372 | { 373 | if (0 == GetModuleFileName(NULL, szPathToExe, ARRAYSIZE(szPathToExe))) 374 | { 375 | return false; 376 | } 377 | if (FAILED(StringCchCopy(szPathToExeDir, ARRAYSIZE(szPathToExeDir), szPathToExe))) 378 | { 379 | return false; 380 | } 381 | *wcsrchr(szPathToExeDir, L'\\') = 0; 382 | if (FAILED(StringCchCopy(szPathToExeToken, ARRAYSIZE(szPathToExeToken), szPathToExe))) 383 | { 384 | return false; 385 | } 386 | for (int i = 0; i < ARRAYSIZE(szPathToExeToken); i++) 387 | { 388 | if (L'\\' == szPathToExeToken[i] || L':' == szPathToExeToken[i]) 389 | { 390 | szPathToExeToken[i] = L'_'; 391 | } 392 | else if (L'\x0' == szPathToExeToken[i]) 393 | { 394 | LOGMESSAGE(L"changed to :%s, length:%d\n", szPathToExeToken, i); 395 | break; 396 | } 397 | } 398 | return true; 399 | } 400 | 401 | //https://support.microsoft.com/en-us/help/243953/how-to-limit-32-bit-applications-to-one-instance-in-visual-c 402 | bool is_another_instance_running() 403 | { 404 | bool ret = false; 405 | //TCHAR szPath[MAX_PATH * 2]; 406 | //if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath))) 407 | if (szPathToExeToken[0]) 408 | { 409 | //size_t length = 0; 410 | //StringCchLength(szPathToExe, ARRAYSIZE(szPathToExe), &length); 411 | 412 | //SECURITY_ATTRIBUTES sa; 413 | //ZeroMemory(&sa, sizeof(sa)); 414 | //sa.nLength = sizeof(SECURITY_ATTRIBUTES); 415 | HANDLE m_hMutex = OpenMutex(MUTEX_ALL_ACCESS, TRUE, szPathToExeToken); 416 | if (NULL == m_hMutex) 417 | { 418 | if (ERROR_FILE_NOT_FOUND != GetLastError()) 419 | { 420 | msg_prompt(/*NULL,*/ L"OpenMutex Failed with unknown error!", 421 | L"Error", 422 | MB_OK | MB_ICONERROR); 423 | } 424 | m_hMutex = CreateMutex(NULL, TRUE, szPathToExeToken); //do early 425 | //DWORD m_dwLastError = GetLastError(); //save for use later... 426 | ret = ERROR_ALREADY_EXISTS == GetLastError(); 427 | if (ret == true) 428 | { 429 | if (ghMutex)CloseHandle(ghMutex); 430 | ghMutex = NULL; 431 | } 432 | } 433 | else 434 | { 435 | ret = true; 436 | } 437 | if (ghMutex)CloseHandle(ghMutex); 438 | ghMutex = m_hMutex; 439 | LOGMESSAGE(L"%d ghMutex: 0x%x\n", ret, ghMutex); 440 | } 441 | return ret; 442 | } 443 | 444 | //https://stackoverflow.com/questions/23814979/c-windows-how-to-get-process-pid-from-its-path 445 | BOOL GetProcessName(LPTSTR szFilename, DWORD dwSize, DWORD dwProcID) 446 | { 447 | BOOLEAN retVal = FALSE; 448 | HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcID); 449 | DWORD dwPathSize = dwSize; 450 | if (hProcess == 0) 451 | return retVal; // You should check for error code, if you are concerned about this 452 | #if VER_PRODUCTBUILD == 7600 453 | retVal = NULL != GetProcessImageFileName(hProcess, szFilename, dwSize); 454 | #else 455 | retVal = QueryFullProcessImageName(hProcess, 0, szFilename, &dwPathSize); 456 | #endif 457 | 458 | CloseHandle(hProcess); 459 | 460 | return retVal; 461 | } 462 | 463 | DWORD GetNamedProcessID(LPCTSTR process_name) 464 | { 465 | const int MAX_PROCESS_NUMBERS = 1024; 466 | DWORD pProcs[MAX_PROCESS_NUMBERS]; 467 | 468 | //DWORD* pProcs = NULL; 469 | //DWORD retVal = 0; 470 | DWORD dwSize = MAX_PROCESS_NUMBERS; 471 | DWORD dwRealSize = 0; 472 | TCHAR szCompareName[MAX_PATH + 1]; 473 | 474 | //dwSize = 1024; 475 | //pProcs = new DWORD[dwSize]; 476 | EnumProcesses(pProcs, dwSize * sizeof(DWORD), &dwRealSize); 477 | dwSize = dwRealSize / sizeof(DWORD); 478 | LOGMESSAGE(L"There are %d processes running", dwSize); 479 | for (DWORD nCount = 0; nCount < dwSize; nCount++) 480 | { 481 | //ZeroMemory(szCompareName, MAX_PATH + 1 * (sizeof(TCHAR))); 482 | ZeroMemory(szCompareName, sizeof(szCompareName)); 483 | if (GetProcessName(szCompareName, MAX_PATH, pProcs[nCount])) 484 | { 485 | if (wcscmp(process_name, szCompareName) == 0) 486 | { 487 | return pProcs[nCount]; 488 | //retVal = pProcs[nCount]; 489 | //delete[] pProcs; 490 | //return retVal; 491 | } 492 | } 493 | } 494 | //delete[] pProcs; 495 | return 0; 496 | } 497 | 498 | /* 499 | * only can be called once 500 | */ 501 | void makeSingleInstance3() 502 | { 503 | if (is_another_instance_running()) 504 | { 505 | LOGMESSAGE(L"is_another_instance_running!\n"); 506 | bool to_exit_now = false; 507 | // check by filepath 508 | if (is_from_self_restart == false && false == is_runas_admin) 509 | { 510 | //TCHAR szPathToExe[MAX_PATH * 2]; 511 | //if (GetModuleFileName(NULL, szPathToExe, ARRAYSIZE(szPathToExe))) 512 | if (szPathToExe[0]) 513 | { 514 | DWORD pid = GetNamedProcessID(szPathToExe); 515 | if (0 != pid) 516 | { 517 | LOGMESSAGE(L"found running CommandTrayHost pid: %d\n", pid); 518 | to_exit_now = true; 519 | } 520 | } 521 | } 522 | // check by mutex 523 | if (false == to_exit_now) 524 | { 525 | DWORD dwWaitResult = WaitForSingleObject(ghMutex, 1000 * 5); 526 | LOGMESSAGE(L"WaitForSingleObject 0x%x 0x%x\n", dwWaitResult, GetLastError()); 527 | if (WAIT_TIMEOUT == dwWaitResult) 528 | { 529 | to_exit_now = true; 530 | } 531 | } 532 | 533 | if (true == to_exit_now) 534 | { 535 | msg_prompt(/*NULL, */L"CommandTrayHost is already running!\n" 536 | L"If you are sure not, you can reboot your computer \n" 537 | L"or move CommandTrayHost.exe to other folder \n" 538 | L"or rename CommandTrayHost.exe", 539 | L"Error", 540 | MB_OK | MB_ICONERROR); 541 | if (ghMutex)CloseHandle(ghMutex); 542 | exit(-1); 543 | } 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /CommandTrayHost/CommandTrayHost.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | XP-Release 22 | Win32 23 | 24 | 25 | XP-Release 26 | x64 27 | 28 | 29 | 30 | 15.0 31 | {18FC42B7-89B4-4972-9D65-C4322F6CC817} 32 | Win32Proj 33 | CommandTrayHost 34 | 10.0 35 | 36 | 37 | Release 38 | 39 | 40 | 41 | Application 42 | true 43 | v142 44 | Unicode 45 | 46 | 47 | Application 48 | false 49 | v142 50 | true 51 | Unicode 52 | 53 | 54 | Application 55 | false 56 | v141_xp 57 | true 58 | Unicode 59 | 60 | 61 | Application 62 | true 63 | v142 64 | Unicode 65 | 66 | 67 | Application 68 | false 69 | v142 70 | true 71 | Unicode 72 | 73 | 74 | Application 75 | false 76 | v141_xp 77 | true 78 | Unicode 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | true 106 | 107 | 108 | true 109 | 110 | 111 | false 112 | 113 | 114 | false 115 | 116 | 117 | false 118 | 119 | 120 | false 121 | 122 | 123 | 124 | Use 125 | Level3 126 | Disabled 127 | true 128 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 129 | $(SolutionDir)third_party\ccronexpr;%(AdditionalIncludeDirectories) 130 | 131 | 132 | Windows 133 | true 134 | 135 | 136 | $(TargetName)$(TargetExt).manifest 137 | 138 | 139 | PerMonitorHighDPIAware 140 | 141 | 142 | 143 | 144 | Use 145 | Level3 146 | Disabled 147 | true 148 | _DEBUG;_WINDOWS;%(PreprocessorDefinitions) 149 | MultiThreadedDebug 150 | $(SolutionDir)third_party\ccronexpr;%(AdditionalIncludeDirectories) 151 | 152 | 153 | Windows 154 | true 155 | 156 | 157 | $(TargetName)$(TargetExt).manifest 158 | PerMonitorHighDPIAware 159 | 160 | 161 | 162 | 163 | Use 164 | Level3 165 | MaxSpeed 166 | true 167 | true 168 | true 169 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 170 | MultiThreaded 171 | $(SolutionDir)third_party\ccronexpr;%(AdditionalIncludeDirectories) 172 | 173 | 174 | Windows 175 | true 176 | true 177 | false 178 | 179 | 180 | $(TargetName)$(TargetExt).manifest 181 | 182 | 183 | PerMonitorHighDPIAware 184 | 185 | 186 | 187 | 188 | Use 189 | Level3 190 | MaxSpeed 191 | true 192 | true 193 | true 194 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 195 | MultiThreaded 196 | $(SolutionDir)third_party\ccronexpr;%(AdditionalIncludeDirectories) 197 | 198 | 199 | Windows 200 | true 201 | true 202 | false 203 | 204 | 205 | 206 | 207 | Use 208 | Level3 209 | MaxSpeed 210 | true 211 | true 212 | true 213 | NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 214 | MultiThreaded 215 | $(SolutionDir)third_party\ccronexpr;%(AdditionalIncludeDirectories) 216 | 217 | 218 | Windows 219 | true 220 | true 221 | false 222 | 223 | 224 | $(TargetName)$(TargetExt).manifest 225 | 226 | 227 | PerMonitorHighDPIAware 228 | 229 | 230 | 231 | 232 | Use 233 | Level3 234 | MaxSpeed 235 | true 236 | true 237 | true 238 | NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 239 | MultiThreaded 240 | $(SolutionDir)third_party\ccronexpr;%(AdditionalIncludeDirectories) 241 | 242 | 243 | Windows 244 | true 245 | true 246 | false 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | Create 280 | Create 281 | Create 282 | Create 283 | Create 284 | Create 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | -------------------------------------------------------------------------------- /CommandTrayHost/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "utils.hpp" 3 | #include "cache.h" 4 | 5 | #ifdef _DEBUG 6 | std::wstring get_utf16(const std::string& str, int codepage) 7 | { 8 | if (str.empty()) return std::wstring(); 9 | int sz = MultiByteToWideChar(codepage, 0, &str[0], (int)str.size(), 0, 0); 10 | std::wstring res(sz, 0); 11 | MultiByteToWideChar(codepage, 0, &str[0], (int)str.size(), &res[0], sz); 12 | return res; 13 | } 14 | 15 | std::wstring string_to_wstring(const std::string& text) 16 | { 17 | return std::wstring(text.begin(), text.end()); 18 | } 19 | 20 | std::wstring s2ws(const std::string& s) 21 | { 22 | int len; 23 | int slength = (int)s.length() + 1; 24 | len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0); 25 | wchar_t* buf = new wchar_t[len]; 26 | MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len); 27 | std::wstring r(buf); 28 | delete[] buf; 29 | return r; 30 | } 31 | #endif 32 | 33 | std::string truncate(std::string str, size_t width, bool show_ellipsis) 34 | { 35 | if (width > 0 && str.length() > width) 36 | { 37 | if (show_ellipsis) 38 | { 39 | return str.substr(0, width) + "..."; 40 | } 41 | else 42 | { 43 | return str.substr(0, width); 44 | } 45 | } 46 | 47 | return str; 48 | } 49 | 50 | // convert UTF-8 string to wstring 51 | std::wstring utf8_to_wstring(const std::string& str) 52 | { 53 | std::wstring_convert> myconv; 54 | return myconv.from_bytes(str); 55 | } 56 | 57 | // convert wstring to UTF-8 string 58 | std::string wstring_to_utf8(const std::wstring& str) 59 | { 60 | std::wstring_convert> myconv; 61 | return myconv.to_bytes(str); 62 | } 63 | 64 | extern TCHAR szPathToExeDir[MAX_PATH * 10]; 65 | 66 | /*std::wstring get_abs_path_old(const std::wstring& path_wstring, const std::wstring& cmd_wstring) { 67 | if (0 == path_wstring.compare(0, 2, L"..") || (path_wstring == L"" && 0 == cmd_wstring.compare(0, 2, L".."))) { 68 | TCHAR abs_path[MAX_PATH * 128]; // 这个必须要求是可写的字符串,不能是const的。 69 | if (NULL == PathCombine(abs_path, szPathToExeDir, path_wstring.c_str())) 70 | { 71 | LOGMESSAGE(L"Copy CTH path failed\n"); 72 | msg_prompt(L"PathCombine Failed", L"Error", MB_OK | MB_ICONERROR); 73 | } 74 | return abs_path; 75 | } 76 | return path_wstring; 77 | }*/ 78 | 79 | /*std::wstring get_abs_path_old2(const std::wstring& path_wstring, const std::wstring& cmd_wstring) 80 | { 81 | TCHAR abs_path[MAX_PATH * 128]; // 这个必须要求是可写的字符串,不能是const的。 82 | if (NULL == PathCombine(abs_path, path_wstring.c_str(), cmd_wstring.c_str())) 83 | { 84 | LOGMESSAGE(L"Copy CTH path failed\n"); 85 | msg_prompt(L"PathCombine Failed", L"Error", MB_OK | MB_ICONERROR); 86 | } 87 | if (abs_path[1] != L':') // if : exist in parameter 88 | { 89 | if (NULL == PathCombine(abs_path, szPathToExeDir, path_wstring.c_str())) 90 | { 91 | LOGMESSAGE(L"Copy CTH path failed\n"); 92 | msg_prompt(L"PathCombine Failed", L"Error", MB_OK | MB_ICONERROR); 93 | } 94 | else 95 | { 96 | return abs_path; 97 | } 98 | } 99 | else if(path_wstring == L"") // path: C:\windows, cmd: system32\cmd.exe 100 | { 101 | PathRemoveFileSpec(abs_path); 102 | return abs_path; 103 | } 104 | return path_wstring; // path: C:\windows, cmd: system32\cmd.exe 105 | }*/ 106 | 107 | std::wstring get_abs_path(const std::wstring& path_wstring, const std::wstring& cmd_wstring) 108 | { 109 | if (path_wstring.length() > 2 && path_wstring[1] == L':') 110 | { 111 | return path_wstring; 112 | } 113 | TCHAR abs_path[MAX_PATH * 128]; 114 | if (path_wstring == L"" && cmd_wstring.length() > 2 && cmd_wstring[1] == L':') 115 | { 116 | StringCchCopy(abs_path, cmd_wstring.length() + 2, cmd_wstring.c_str()); 117 | PathRemoveFileSpec(abs_path); 118 | return abs_path; 119 | } 120 | if (NULL == PathCombine(abs_path, szPathToExeDir, path_wstring.c_str())) 121 | { 122 | LOGMESSAGE(L"Copy CTH path failed\n"); 123 | msg_prompt(/*NULL, */L"PathCombine Failed", L"Error", MB_OK | MB_ICONERROR); 124 | return L""; 125 | } 126 | else 127 | { 128 | return abs_path; 129 | } 130 | } 131 | 132 | std::wstring get_abs_working_directory(const std::wstring& path_wstring, const std::wstring& working_directory_wstring) 133 | { 134 | if (working_directory_wstring.length() > 2 && working_directory_wstring[1] == L':') 135 | { 136 | return working_directory_wstring; 137 | } 138 | TCHAR abs_path[MAX_PATH * 128]; 139 | if (working_directory_wstring.length() >= 1 && working_directory_wstring[0] == L'>') 140 | { 141 | if (NULL == PathCombine(abs_path, szPathToExeDir, working_directory_wstring.c_str() + 1)) 142 | { 143 | LOGMESSAGE(L"Copy CTH path failed\n"); 144 | msg_prompt(/*NULL, */L"PathCombine Failed", L"Error", MB_OK | MB_ICONERROR); 145 | return L""; 146 | } 147 | 148 | return abs_path; 149 | } 150 | 151 | if (NULL == PathCombine(abs_path, path_wstring.c_str(), working_directory_wstring.c_str())) 152 | { 153 | LOGMESSAGE(L"Copy CTH path failed\n"); 154 | msg_prompt(/*NULL, */L"PathCombine Failed", L"Error", MB_OK | MB_ICONERROR); 155 | return L""; 156 | } 157 | 158 | return abs_path; 159 | } 160 | 161 | /* 162 | // convert UTF-8 string to u16string 163 | std::u16string utf8_to_u16string(const std::string& str) 164 | { 165 | std::wstring_convert, char16_t> myconv; 166 | return myconv.from_bytes(str); 167 | } 168 | 169 | // convert u16string to UTF-8 string 170 | std::string u16string_to_utf8(const std::u16string& str) 171 | { 172 | std::wstring_convert, char16_t> myconv; 173 | return myconv.to_bytes(str); 174 | } 175 | */ 176 | 177 | bool printf_to_bufferA(char* dst, size_t max_len, size_t& cursor, PCSTR fmt, ...) 178 | { 179 | va_list args; 180 | va_start(args, fmt); 181 | HRESULT hr = StringCchVPrintfA( 182 | dst + cursor, 183 | max_len - cursor, 184 | fmt, 185 | args 186 | ); 187 | va_end(args); 188 | if (FAILED(hr)) 189 | { 190 | LOGMESSAGE(L"StringCchVPrintfA failed\n"); 191 | return false; 192 | } 193 | size_t len = 0; 194 | hr = StringCchLengthA(dst + cursor, max_len - cursor, &len); 195 | if (FAILED(hr)) 196 | { 197 | LOGMESSAGE(L"StringCchLengthA failed\n"); 198 | return false; 199 | } 200 | cursor += len; 201 | return true; 202 | } 203 | 204 | //https://stackoverflow.com/questions/735204/convert-a-string-in-c-to-upper-case 205 | //char ascii_tolower_char(const char c) { 206 | // return ('A' <= c && c <= 'Z') ? c ^ 0x20 : c; // ^ autovectorizes to PXOR: runs on more ports than paddb 207 | //} 208 | 209 | char ascii_toupper_char(const char c) { 210 | return ('a' <= c && c <= 'z') ? c ^ 0x20 : c; // ^ autovectorizes to PXOR: runs on more ports than paddb 211 | } 212 | 213 | int str_icmp(const char*s1, const char*s2) 214 | { 215 | for (int i = 0; /*s1[i] != 0 &&*/ s2[i] != 0; i++) 216 | { 217 | char c1 = ascii_toupper_char(s1[i]), c2 = ascii_toupper_char(s2[i]); 218 | if (c1 != c2)return c1 - c2; 219 | } 220 | return 0; 221 | } 222 | 223 | bool is_valid_vk(char c) 224 | { 225 | if ('0' <= c && c <= '9')return true; 226 | if ('A' <= c && c <= 'Z')return true; 227 | return false; 228 | } 229 | 230 | bool get_vk_from_string(const char* s, UINT& fsModifiers, UINT& vk) 231 | { 232 | if (!s || !s[0])return false; 233 | extern bool repeat_mod_hotkey; 234 | // https://stackoverflow.com/questions/6103059/registerhotkey-only-working-in-windows-7-not-in-xp-server-2003 235 | #if VER_PRODUCTBUILD == 7600 236 | fsModifiers = NULL; 237 | #else 238 | fsModifiers = repeat_mod_hotkey ? NULL : MOD_NOREPEAT; 239 | #endif 240 | int idx = 0; 241 | while (s[idx]) 242 | { 243 | if (0 == str_icmp(s + idx, "alt")) 244 | { 245 | idx += 3; 246 | fsModifiers |= MOD_ALT; 247 | } 248 | else if (0 == str_icmp(s + idx, "ctrl")) 249 | { 250 | idx += 4; 251 | fsModifiers |= MOD_CONTROL; 252 | } 253 | else if (0 == str_icmp(s + idx, "shift")) 254 | { 255 | idx += 5; 256 | fsModifiers |= MOD_SHIFT; 257 | } 258 | else if (0 == str_icmp(s + idx, "win")) 259 | { 260 | idx += 3; 261 | fsModifiers |= MOD_WIN; 262 | } 263 | //https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx 264 | else if (0 == str_icmp(s + idx, "0x")) // support 0x12 265 | { 266 | idx += 2; 267 | char c1 = s[idx], c2 = s[idx + 1]; 268 | 269 | if ('a' <= c1 && c1 <= 'f')c1 -= 'a' - 10; 270 | else if ('A' <= c1 && c1 <= 'F')c1 -= 'A' - 10; 271 | else if ('0' <= c1 && c1 <= '9')c1 -= '0'; 272 | else return false; 273 | 274 | if ('a' <= c2 && c2 <= 'f')c2 -= 'a' - 10; 275 | else if ('A' <= c2 && c2 <= 'F')c2 -= 'A' - 10; 276 | else if ('0' <= c2 && c2 <= '9')c2 -= '0'; 277 | else return false; 278 | 279 | if (0 <= c1 && c1 <= 15 && 0 <= c2 && c2 <= 15) 280 | { 281 | vk = 0x10 * c1 + c2; 282 | idx += 2; 283 | } 284 | else 285 | { 286 | return false; 287 | } 288 | } 289 | else if (0 == str_icmp(s + idx, "++")) 290 | { 291 | vk = VK_OEM_PLUS; 292 | idx += 2; 293 | } 294 | else if (0 == str_icmp(s + idx, "+-")) 295 | { 296 | vk = VK_OEM_MINUS; 297 | idx += 2; 298 | } 299 | else if (s[idx] == ' ' && s[idx + 1] == 0x0) 300 | { 301 | if (idx && s[idx - 1] == '+') 302 | { 303 | vk = VK_SPACE; 304 | } 305 | idx++; 306 | } 307 | else if (is_valid_vk(ascii_toupper_char(s[idx]))) 308 | { 309 | vk = ascii_toupper_char(s[idx]); 310 | idx++; 311 | } 312 | else if (s[idx] == ' ' || s[idx] == '+') 313 | { 314 | idx++; 315 | } 316 | else 317 | { 318 | return false; 319 | } 320 | } 321 | return true; 322 | } 323 | 324 | bool registry_hotkey(const char* s, int id, PCWSTR msg, bool show_error) 325 | { 326 | extern HWND hWnd; 327 | //LOGMESSAGE(L"hWnd:0x%x\n", hWnd); 328 | //assert(hWnd); 329 | UINT fsModifiers, vk; 330 | bool success = get_vk_from_string(s, fsModifiers, vk); 331 | if (!success) 332 | { 333 | if (show_error) 334 | { 335 | msg_prompt(//NULL, 336 | (msg + std::wstring(L" string parse error!")).c_str(), 337 | L"Hotkey String Error", 338 | MB_OK | MB_ICONWARNING 339 | ); 340 | } 341 | return false; 342 | } 343 | LOGMESSAGE(L"%s hWnd:0x%x id:0x%x fsModifiers:0x%x vk:0x%x\n", msg, hWnd, id, fsModifiers, vk); 344 | LOGMESSAGE(L"GetCurrentThreadId:%d\n", GetCurrentThreadId()); 345 | if (0 == RegisterHotKey(hWnd, id, fsModifiers, vk)) 346 | { 347 | errno_t error_code = GetLastError(); 348 | LOGMESSAGE(L"error_code:0x%x\n", error_code); 349 | if (show_error) 350 | { 351 | msg_prompt(//NULL, 352 | (msg + (L"\n Error code:" + std::to_wstring(error_code))).c_str(), 353 | L"Hotkey Register HotKey Error", 354 | MB_OK | MB_ICONWARNING 355 | ); 356 | } 357 | return false; 358 | } 359 | return true; 360 | } 361 | 362 | // https://stackoverflow.com/questions/8991192/check-filesize-without-opening-file-in-c 363 | int64_t FileSize(PCWSTR name) 364 | { 365 | HANDLE hFile = CreateFile(name, GENERIC_READ, 366 | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 367 | FILE_ATTRIBUTE_NORMAL, NULL); 368 | if (hFile == INVALID_HANDLE_VALUE) 369 | return -1; // error condition, could call GetLastError to find out more 370 | 371 | LARGE_INTEGER size; 372 | if (!GetFileSizeEx(hFile, &size)) 373 | { 374 | CloseHandle(hFile); 375 | return -1; // error condition, could call GetLastError to find out more 376 | } 377 | 378 | CloseHandle(hFile); 379 | return size.QuadPart; 380 | } 381 | 382 | 383 | bool json_object_has_member(const nlohmann::json& root, PCSTR query_string) 384 | { 385 | try 386 | { 387 | root.at(query_string); 388 | } 389 | #ifdef _DEBUG 390 | catch (nlohmann::json::out_of_range& e) 391 | #else 392 | catch (nlohmann::json::out_of_range&) 393 | #endif 394 | { 395 | LOGMESSAGE(L"out_of_range %S\n", e.what()); 396 | return false; 397 | } 398 | catch (...) 399 | { 400 | msg_prompt(//NULL, 401 | L"json_object_has_member error", 402 | L"Type Error", 403 | MB_OK | MB_ICONERROR 404 | ); 405 | return false; 406 | } 407 | return true; 408 | } 409 | 410 | #if _DEBUG2 411 | 412 | //https://stackoverflow.com/questions/40013355/how-to-merge-two-json-file-using-rapidjson 413 | void rapidjson_merge_object(rapidjson::Value &dstObject, rapidjson::Value &srcObject, rapidjson::Document::AllocatorType &allocator) 414 | { 415 | for (auto srcIt = srcObject.MemberBegin(); srcIt != srcObject.MemberEnd(); ++srcIt) 416 | { 417 | auto dstIt = dstObject.FindMember(srcIt->name); 418 | if (dstIt != dstObject.MemberEnd()) 419 | { 420 | assert(srcIt->value.GetType() == dstIt->value.GetType()); 421 | if (srcIt->value.IsArray()) 422 | { 423 | for (auto arrayIt = srcIt->value.Begin(); arrayIt != srcIt->value.End(); ++arrayIt) 424 | { 425 | dstIt->value.PushBack(*arrayIt, allocator); 426 | } 427 | } 428 | else if (srcIt->value.IsObject()) 429 | { 430 | rapidjson_merge_object(dstIt->value, srcIt->value, allocator); 431 | } 432 | else 433 | { 434 | dstIt->value = srcIt->value; 435 | } 436 | } 437 | else 438 | { 439 | dstObject.AddMember(srcIt->name, srcIt->value, allocator); 440 | } 441 | } 442 | } 443 | 444 | #endif 445 | 446 | //HWND WINAPI GetForegroundWindow(void); 447 | 448 | /*bool operator != (const RECT& rct1, const RECT& rct2) 449 | { 450 | return rct1.left != rct2.left || rct1.top != rct2.top || rct1.right != rct2.right || rct1.bottom != rct2.bottom; 451 | }*/ 452 | 453 | int msg_prompt( 454 | //_In_opt_ HWND hWnd, 455 | _In_opt_ LPCTSTR lpText, 456 | _In_opt_ LPCTSTR lpCaption, 457 | _In_ UINT uType 458 | ) 459 | { 460 | extern HWND hWnd; 461 | SetForegroundWindow(hWnd); 462 | return MessageBox(hWnd, lpText, lpCaption, uType | MB_TOPMOST | MB_SETFOREGROUND); 463 | } 464 | 465 | BOOL get_hicon(PCWSTR filename, int icon_size, HICON& hIcon, bool share) 466 | { 467 | if (TRUE == PathFileExists(filename)) 468 | { 469 | LOGMESSAGE(L"icon file eixst %s\n", filename); 470 | hIcon = reinterpret_cast(LoadImage(NULL, 471 | filename, 472 | IMAGE_ICON, 473 | icon_size ? icon_size : 256, 474 | icon_size ? icon_size : 256, 475 | share ? (LR_LOADFROMFILE | LR_SHARED) : LR_LOADFROMFILE) 476 | ); 477 | if (hIcon == NULL) 478 | { 479 | LOGMESSAGE(L"Load IMAGE_ICON failed! error:0x%x\n", GetLastError()); 480 | return FALSE; 481 | } 482 | return TRUE; 483 | /*hIcon = reinterpret_cast(LoadImage( // returns a HANDLE so we have to cast to HICON 484 | NULL, // hInstance must be NULL when loading from a file 485 | wicon.c_str(), // the icon file name 486 | IMAGE_ICON, // specifies that the file is an icon 487 | 16, // width of the image (we'll specify default later on) 488 | 16, // height of the image 489 | LR_LOADFROMFILE //| // we want to load a file (as opposed to a resource) 490 | //LR_DEFAULTSIZE | // default metrics based on the type (IMAGE_ICON, 32x32) 491 | //LR_SHARED // let the system release the handle when it's no longer used 492 | ));*/ 493 | } 494 | return FALSE; 495 | } 496 | 497 | // https://stackoverflow.com/questions/3269390/how-to-get-hwnd-of-window-opened-by-shellexecuteex-hprocess 498 | struct ProcessWindowsInfo 499 | { 500 | DWORD ProcessID; 501 | std::vector Windows; 502 | 503 | ProcessWindowsInfo(DWORD const AProcessID) 504 | : ProcessID(AProcessID) 505 | { 506 | } 507 | }; 508 | 509 | BOOL __stdcall EnumProcessWindowsProc(HWND hwnd, LPARAM lParam) 510 | { 511 | ProcessWindowsInfo *Info = reinterpret_cast(lParam); 512 | DWORD WindowProcessID; 513 | 514 | GetWindowThreadProcessId(hwnd, &WindowProcessID); 515 | 516 | if (WindowProcessID == Info->ProcessID) 517 | Info->Windows.push_back(hwnd); 518 | 519 | return true; 520 | } 521 | 522 | BOOL get_alpha(HWND hwnd, BYTE& alpha, bool no_exstyle_return) 523 | { 524 | alpha = 0; 525 | DWORD dwExStyle = GetWindowLong(hwnd, GWL_EXSTYLE); 526 | if ((dwExStyle | WS_EX_LAYERED) == 0) 527 | { 528 | if (no_exstyle_return)return FALSE; 529 | SetWindowLong(hwnd, GWL_EXSTYLE, dwExStyle | WS_EX_LAYERED); 530 | } 531 | if (GetLayeredWindowAttributes(hwnd, NULL, &alpha, NULL)) 532 | { 533 | if (0 == (dwExStyle & WS_EX_LAYERED) && alpha == 0)alpha = 255; 534 | return TRUE; 535 | } 536 | return FALSE; 537 | } 538 | 539 | HWND GetHwnd(HANDLE hProcess, size_t& num_of_windows, int idx) 540 | { 541 | if (hProcess == NULL)return NULL; 542 | WaitForInputIdle(hProcess, INFINITE); 543 | 544 | ProcessWindowsInfo Info(GetProcessId(hProcess)); 545 | 546 | if (!EnumWindows((WNDENUMPROC)EnumProcessWindowsProc, reinterpret_cast(&Info))) 547 | { 548 | LOGMESSAGE(L"GetLastError:0x%x\n", GetLastError()); 549 | } 550 | num_of_windows = Info.Windows.size(); 551 | LOGMESSAGE(L"hProcess:0x%x GetProcessId:%d num_of_windows size: %d\n", 552 | reinterpret_cast(hProcess), 553 | GetProcessId(hProcess), 554 | num_of_windows 555 | ); 556 | if (num_of_windows > 0) 557 | { 558 | return Info.Windows[idx]; 559 | } 560 | else 561 | { 562 | return NULL; 563 | } 564 | } 565 | 566 | int get_caption_from_hwnd(HWND hwnd, std::wstring& caption) 567 | { 568 | int ret; 569 | const int max_caption_length = 64; 570 | TCHAR caption_buffer[max_caption_length + 2]; 571 | int len = GetWindowTextLength(hwnd); 572 | if (len > 0) 573 | { 574 | //PWSTR caption_buffer=HeapAlloc(GetProcessHeap(), 0, sizeof(char) * (len + 1)); 575 | if (len > max_caption_length)len = max_caption_length; 576 | GetWindowText(hwnd, caption_buffer, len + 1); 577 | LOGMESSAGE(L"GetWindowText:%s\n", caption_buffer); 578 | ret = 1; 579 | } 580 | else 581 | { 582 | if (0 == len)len = max_caption_length; 583 | GetClassName(hwnd, caption_buffer, len); 584 | LOGMESSAGE(L"GetClassName:%s\n", caption_buffer); 585 | ret = 2; 586 | } 587 | caption = caption_buffer; 588 | return ret; 589 | } 590 | 591 | BOOL is_hwnd_valid_caption(HWND hwnd, PCWSTR caption_, DWORD pid_) 592 | { 593 | if (!IsWindow(hwnd)) return FALSE; 594 | if (pid_) 595 | { 596 | DWORD pid = NULL; 597 | GetWindowThreadProcessId(hwnd, &pid); 598 | return pid == pid_; 599 | } 600 | else if (caption_ != NULL) 601 | { 602 | std::wstring caption_wstring; 603 | get_caption_from_hwnd(hwnd, caption_wstring); 604 | 605 | return caption_wstring == caption_; 606 | } 607 | else 608 | { 609 | return FALSE; 610 | } 611 | } 612 | 613 | #define TA_FAILED 0 614 | #define TA_SUCCESS_CLEAN 1 615 | #define TA_SUCCESS_KILL 2 616 | #define TA_SUCCESS_16 3 617 | 618 | BOOL CALLBACK TerminateAppEnum(HWND hwnd, LPARAM lParam) 619 | { 620 | DWORD dwID; 621 | 622 | GetWindowThreadProcessId(hwnd, &dwID); 623 | 624 | if (dwID == (DWORD)lParam) 625 | { 626 | PostMessage(hwnd, WM_CLOSE, 0, 0); 627 | } 628 | 629 | return TRUE; 630 | } 631 | 632 | /*---------------------------------------------------------------- 633 | DWORD WINAPI TerminateApp( DWORD dwPID, DWORD dwTimeout ) 634 | 635 | Purpose: 636 | Shut down a 32-Bit Process (or 16-bit process under Windows 95) 637 | 638 | Parameters: 639 | dwPID 640 | Process ID of the process to shut down. 641 | 642 | dwTimeout 643 | Wait time in milliseconds before shutting down the process. 644 | 645 | Return Value: 646 | TA_FAILED - If the shutdown failed. 647 | TA_SUCCESS_CLEAN - If the process was shutdown using WM_CLOSE. 648 | TA_SUCCESS_KILL - if the process was shut down with 649 | TerminateProcess(). 650 | NOTE: See header for these defines. 651 | ----------------------------------------------------------------*/ 652 | DWORD WINAPI TerminateApp(DWORD dwPID, DWORD dwTimeout) 653 | { 654 | HANDLE hProc; 655 | DWORD dwRet; 656 | 657 | // If we can't open the process with PROCESS_TERMINATE rights, 658 | // then we give up immediately. 659 | hProc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, 660 | dwPID); 661 | 662 | if (hProc == NULL) 663 | { 664 | return TA_FAILED; 665 | } 666 | 667 | // TerminateAppEnum() posts WM_CLOSE to all windows whose PID 668 | // matches your process's. 669 | EnumWindows((WNDENUMPROC)TerminateAppEnum, (LPARAM)dwPID); 670 | 671 | // Wait on the handle. If it signals, great. If it times out, 672 | // then you kill it. 673 | if (WaitForSingleObject(hProc, dwTimeout) != WAIT_OBJECT_0) 674 | dwRet = (TerminateProcess(hProc, 0) ? TA_SUCCESS_KILL : TA_FAILED); 675 | else 676 | dwRet = TA_SUCCESS_CLEAN; 677 | 678 | CloseHandle(hProc); 679 | 680 | return dwRet; 681 | } 682 | 683 | //extern nlohmann::json global_stat; 684 | //extern size_t number_of_configs; 685 | 686 | extern bool enable_cache; 687 | //extern bool conform_cache_expire; 688 | extern bool disable_cache_position; 689 | extern bool disable_cache_size; 690 | extern bool disable_cache_enabled; 691 | //extern bool disable_cache_show; 692 | extern bool is_cache_valid; 693 | 694 | //extern BOOL isZHCN, isENUS; 695 | 696 | 697 | #ifdef _DEBUG_PROCESS_TREE 698 | // not working for nginx continuous fork 699 | #ifdef _DEBUG 700 | bool kill_child_tree(int dep, DWORD pid, DWORD timeout, PCWSTR name) 701 | #else 702 | bool kill_child_tree(int dep, DWORD pid, DWORD timeout) 703 | #endif 704 | { 705 | LOGMESSAGE(L"kill_child_tree running %s PID=%d", name, pid); 706 | PROCESSENTRY32 pe; 707 | 708 | memset(&pe, 0, sizeof(PROCESSENTRY32)); 709 | pe.dwSize = sizeof(PROCESSENTRY32); 710 | 711 | HANDLE hSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 712 | 713 | if (::Process32First(hSnap, &pe)) 714 | { 715 | BOOL bContinue = TRUE; 716 | 717 | // kill child processes 718 | while (bContinue) 719 | { 720 | // only kill child processes 721 | if (pe.th32ParentProcessID == pid) 722 | { 723 | LOGMESSAGE(L"dep:%d found child pid PID=%d", dep, pe.th32ProcessID); 724 | 725 | #ifdef _DEBUG 726 | kill_child_tree(dep + 1, pe.th32ProcessID, timeout, name); 727 | #else 728 | kill_child_tree(dep + 1, pe.th32ProcessID, timeout); 729 | #endif 730 | 731 | /*if (TA_FAILED == TerminateApp(pe.th32ProcessID, timeout)) 732 | { 733 | LOGMESSAGE(L"TerminateApp %s pid: %d failed!! File = %S Line = %d Func=%S Date=%S Time=%S\n", 734 | name, pid, 735 | __FILE__, __LINE__, __FUNCTION__, __DATE__, __TIME__); 736 | //TerminateProcess(pe.th32ProcessID, 0); 737 | HANDLE hChildProc = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID); 738 | 739 | if (hChildProc) 740 | { 741 | ::TerminateProcess(hChildProc, 1); 742 | ::CloseHandle(hChildProc); 743 | } 744 | }*/ 745 | } 746 | 747 | bContinue = ::Process32Next(hSnap, &pe); 748 | } 749 | 750 | // kill the main process 751 | if (dep > 0) 752 | { 753 | LOGMESSAGE(L"killing pid PID=%d", pid); 754 | if (TA_FAILED == TerminateApp(pid, timeout)) 755 | { 756 | LOGMESSAGE(L"TerminateApp %s pid: %d failed!! File = %S Line = %d Func=%S Date=%S Time=%S\n", 757 | name, pid, 758 | __FILE__, __LINE__, __FUNCTION__, __DATE__, __TIME__); 759 | HANDLE hChildProc = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); 760 | 761 | if (hChildProc) 762 | { 763 | ::TerminateProcess(hChildProc, 1); 764 | ::CloseHandle(hChildProc); 765 | } 766 | } 767 | } 768 | return true; 769 | } 770 | return false; 771 | } 772 | 773 | #endif 774 | 775 | #ifdef _DEBUG 776 | void check_and_kill(HANDLE hProcess, DWORD pid, DWORD timeout, PCWSTR name, bool kill_process_tree, bool is_update_cache) 777 | #else 778 | void check_and_kill(HANDLE hProcess, DWORD pid, DWORD timeout, bool kill_process_tree, bool is_update_cache) 779 | #endif 780 | { 781 | assert(GetProcessId(hProcess) == pid); 782 | if (GetProcessId(hProcess) == pid) 783 | { 784 | if (kill_process_tree) 785 | { 786 | std::wstring system_cmd = L"TASKKILL /F /T /PID " + std::to_wstring(pid); 787 | _wsystem(system_cmd.c_str()); 788 | #ifdef _DEBUG_PROCESS_TREE 789 | #ifdef _DEBUG 790 | kill_child_tree(0, pid, timeout, name); 791 | #else 792 | kill_child_tree(0, pid, timeout); 793 | #endif 794 | #endif 795 | } 796 | else 797 | { 798 | if (TA_FAILED == TerminateApp(pid, timeout)) 799 | { 800 | LOGMESSAGE(L"TerminateApp %s pid: %d failed!! File = %S Line = %d Func=%S Date=%S Time=%S\n", 801 | name, pid, 802 | __FILE__, __LINE__, __FUNCTION__, __DATE__, __TIME__); 803 | TerminateProcess(hProcess, 0); 804 | } 805 | else 806 | { 807 | LOGMESSAGE(L"TerminateApp %S pid: %d successed. File = %S Line = %d Func=%S Date=%S Time=%S\n", 808 | name, pid, 809 | __FILE__, __LINE__, __FUNCTION__, __DATE__, __TIME__); 810 | } 811 | } 812 | CloseHandle(hProcess); 813 | } 814 | #ifdef _DEBUG 815 | else 816 | { 817 | msg_prompt(L"It should never here. GetProcessId(hProcess) != pid", L"Why this happened?"); 818 | } 819 | #endif 820 | if (is_update_cache && enable_cache && !disable_cache_enabled) 821 | { 822 | update_cache("enabled", false, cEnabled); 823 | } 824 | } 825 | 826 | BOOL set_wnd_pos( 827 | HWND hWnd, 828 | int x, int y, int cx, int cy 829 | , bool top_most 830 | , bool use_pos 831 | , bool use_size 832 | //, bool is_show 833 | ) 834 | { 835 | UINT uFlags = SWP_NOACTIVATE | SWP_ASYNCWINDOWPOS; 836 | if (!use_pos)uFlags |= SWP_NOMOVE; 837 | if (!use_size)uFlags |= SWP_NOSIZE; 838 | //if (is_show)uFlags |= SWP_SHOWWINDOW; 839 | //else uFlags |= SWP_HIDEWINDOW; 840 | if (!top_most)uFlags |= SWP_NOZORDER; 841 | return SetWindowPos(hWnd, 842 | top_most ? HWND_TOPMOST : HWND_NOTOPMOST, 843 | x, 844 | y, 845 | cx, 846 | cy, 847 | uFlags 848 | ); 849 | } 850 | 851 | BOOL set_wnd_alpha(HWND hWnd, BYTE bAlpha) 852 | { 853 | SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED); 854 | SetLayeredWindowAttributes(hWnd, 0, bAlpha, LWA_ALPHA); 855 | SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); 856 | return TRUE; 857 | } 858 | 859 | #ifdef _DEBUG 860 | VOID CALLBACK IconSendAsyncProc(__in HWND hwnd, 861 | __in UINT uMsg, 862 | __in ULONG_PTR dwData, 863 | __in LRESULT lResult); 864 | #endif 865 | 866 | BOOL set_wnd_icon(HWND hWnd, HICON hIcon) 867 | { 868 | SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); 869 | SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); 870 | //SendMessageCallback(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon, IconSendAsyncProc, reinterpret_cast(hIcon)); 871 | errno_t err = GetLastError(); 872 | LOGMESSAGE(L"SendMessage error_code:0x%x\n", err); 873 | return 0 == err; 874 | } 875 | 876 | // https://stackoverflow.com/questions/2798922/storage-location-of-yellow-blue-shield-icon 877 | BOOL GetStockIcon(HICON& outHicon) 878 | { 879 | #if VER_PRODUCTBUILD == 7600 880 | #else 881 | SHSTOCKICONINFO sii; 882 | ZeroMemory(&sii, sizeof(sii)); 883 | sii.cbSize = sizeof(sii); 884 | if (S_OK == SHGetStockIconInfo(SIID_SHIELD, SHGSI_ICON | SHGSI_SMALLICON, &sii)) 885 | { 886 | outHicon = sii.hIcon; 887 | return TRUE; 888 | } 889 | #endif 890 | return FALSE; 891 | 892 | /* 893 | SHSTOCKICONINFO sii; 894 | sii.cbSize = sizeof(sii); 895 | SHGetStockIconInfo(SIID_SHIELD, SHGSI_ICONLOCATION, &sii); 896 | HICON hiconShield = ExtractIconEx(sii. ...); 897 | 898 | SHSTOCKICONINFO sii; 899 | ZeroMemory(&sii, sizeof(sii)); 900 | sii.cbSize = sizeof(sii); 901 | SHGetStockIconInfo(SIID_SHIELD, SHGSI_ICONLOCATION, &sii); 902 | 903 | HICON ico; 904 | SHDefExtractIcon(sii.szPath, sii.iIcon, 0, &ico, NULL, IconSize); // IconSize=256 905 | 906 | return hiconShield; 907 | */ 908 | } 909 | 910 | // http://www.programmersheaven.com/discussion/74164/converting-icon-to-bitmap-hicon-hbitmap 911 | HBITMAP BitmapFromIcon(HICON hIcon) 912 | { 913 | HDC hDC = CreateCompatibleDC(NULL); 914 | HBITMAP hBitmap = CreateCompatibleBitmap(hDC, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)); 915 | HBITMAP hOldBitmap = (HBITMAP)SelectObject(hDC, hBitmap); 916 | DrawIcon(hDC, 0, 0, hIcon); 917 | SelectObject(hDC, hOldBitmap); 918 | DeleteDC(hDC); 919 | return hBitmap; 920 | } 921 | 922 | 923 | #ifdef _DEBUG 924 | //https://stackoverflow.com/questions/5058543/sendmessagecallback-usage-example 925 | VOID CALLBACK IconSendAsyncProc(__in HWND hwnd, 926 | __in UINT uMsg, 927 | __in ULONG_PTR dwData, // This is *the* 0 928 | __in LRESULT lResult) // The result from the callee 929 | { 930 | // Whohoo! It called me back! 931 | DestroyIcon(reinterpret_cast(dwData)); 932 | } 933 | #endif 934 | 935 | 936 | #ifdef _DEBUG 937 | //only work for current process 938 | //http://ntcoder.com/bab/2007/07/24/changing-console-application-window-icon-at-runtime/ 939 | void ChangeIcon(const HICON hNewIcon) 940 | { 941 | // Load kernel 32 library 942 | HMODULE hMod = LoadLibrary(_T("Kernel32.dll")); 943 | assert(hMod); 944 | if (hMod == NULL) 945 | { 946 | return; 947 | } 948 | 949 | // Load console icon changing procedure 950 | typedef DWORD(__stdcall *SCI)(HICON); 951 | SCI pfnSetConsoleIcon = reinterpret_cast(GetProcAddress(hMod, "SetConsoleIcon")); 952 | assert(pfnSetConsoleIcon); 953 | if (pfnSetConsoleIcon != NULL) 954 | { 955 | // Call function to change icon 956 | pfnSetConsoleIcon(hNewIcon); 957 | } 958 | 959 | FreeLibrary(hMod); 960 | }// End ChangeIcon 961 | #endif 962 | --------------------------------------------------------------------------------