防止系統鎖屏-python、C++實現!
一、背景
作為一個開發,我的電腦經常是一個禮拜不關機,甚至時間更久,不知道在其他人看來這是不是一個常規操作。在日常工作中,我們的電腦也是一直處於非鎖屏狀態,出於對個人工作成果的安全性保護,我們公司給每個人的電腦上下發了一個組策略(屬於強制下發,抗議無效), 5min不對電腦進行操作,電腦就鎖屏 ,這可真是令人操蛋,出去上個廁所的功夫電腦就鎖屏啦、和別人討論問題的功夫電腦又鎖屏了,作為一個開發,這真不能忍。
最近一直在學習 python ,剛好接觸到了 python 寫 windows 服務相關的一些東西,嘿嘿,5分鐘不操作電腦鎖屏是吧,那麼我們在無任何操作下2分鐘給他模擬一次鍵盤或者滑鼠操作可好。
二、模擬滑鼠、鍵盤事件
要寫一個 windows 服務也是比較簡單的,只需要繼承自win32serviceutil.ServiceFramework這個類,然後實現相關方法即可,主要的方法是SvcDoRun,服務啟動後,該方法處於啟用狀態,該方法結束服務退出
具體的實現方式可參考 Python-定時爬取指定城市天氣(二)-郵件提醒 文章中的第三小節,優化定時任務。
1、 python 實現
這裡我只貼出關鍵程式碼,服務的整體框架不在細說,不會的同學請看這裡 Python-定時爬取指定城市天氣(二)-郵件提醒
a、 python 服務
首先判斷滑鼠2分鐘內是否有操作,我們需要能獲取到當前滑鼠位置的函式, pyautogui 是一個 python 的自動化庫,滿足我們的需求,該庫提供了豐富的滑鼠、鍵盤操作,使用該庫,首先就得使用pip進行安裝
pip install pyautogui
使用方式如下,x和y即是當前滑鼠相對於螢幕左上角(0,0)的座標
import pyautogui as pag x, y = pag.position() #返回滑鼠的座標
模擬滑鼠、鍵盤操作,無非是滑鼠點選、移動、鍵盤按下等,這些 pyautogui 都已經提供,看名字就知道什麼意思,這裡我們先進行了滑鼠點選,預設是左鍵,然後移動了滑鼠位置,並且在最後按下了鍵盤上的esc鍵
pag.click() pag.moveTo(x + 10, y + 10, 0.1) pag.moveTo(x, y, 0.1) writeLog('模擬一次滑鼠移動 ')# pag.press('esc') writeLog('模擬點選esc ')#
完整的SvcDoRUn函式如下
def SvcDoRun(self): #what to do# prev_time = datetime.datetime.now() oldx = 0 oldy = 0 while self.run: x, y = pag.position() #返回滑鼠的座標 now_time = datetime.datetime.now() if x == oldx and y == oldy: stay_seconds = (now_time - prev_time).seconds if stay_seconds >= 60: prev_time = now_time pag.click() pag.moveTo(x + 10, y + 10, 0.1) pag.moveTo(x, y, 0.1) writeLog('模擬一次滑鼠移動 ')# pag.press('esc') writeLog('模擬點選esc ')# else: #更新舊座標 最後一次移動滑鼠時間 oldx = x oldy = y prev_time = now_time #os.system('cls')#清楚螢幕 stay_seconds = (now_time - prev_time).seconds writeLog('滑鼠{}秒未移動 '.format(stay_seconds))#列印座標 posStr = "Position:" + str(x).rjust(4) + ',' + str(y).rjust(4) writeLog(posStr + ' ')#列印座標 time.sleep(2)
服務函式寫完了,接下來是打包服務的過程,並啟動服務
1. 打包服務成一個exe,pyinstaller -F aaa.py 2. 安裝服務 python aaa.exe install 3. 啟動服務 python aaa.exe start 4. 停止服務 python aaa.exe stop 5. 移除服務 python aaa.exe remove
執行上述流程的1、2和3,服務就已經被成功啟動,但不幸的是發現 pag.position() 返回的座標一直是0,各種測試都不對,開始懷疑是服務裡可能找不到 pyautogui 資源導致失敗,後來在網上找了另一種或許滑鼠位置的函式
def get_mouse_point(): po = POINT() windll.user32.GetCursorPos(byref(po)) return int(po.x), int(po.y)
經過測試,該函式單獨執行時沒有問題,放在服務裡拿到的座標還是(0, 0),寫服務的路子算是泡湯啦
為了更好的查詢服務的執行狀態,我們這裡把服務的執行時狀態解除安裝了一個檔案裡,寫日誌程式碼如下
#寫日誌 def writeLog(msg): try: f = open('./prevent_lock_screen.log', 'a', encoding = 'utf-8') f.write(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + ':' + msg) f.close() except BaseException: pass
b、 python 函式
python 服務的方式暫時算是中斷了,但是我們還是不能放棄啊,經過嘗試, 把執行在服務裡的程式碼拿出來放在正常 python 檔案裡執行還是好使的 。不明所以啊, ++哪位大神如果知道服務裡的程式碼為什麼執行失敗,還請評論區支出,不勝感激。。。++
為了防止電腦自動鎖屏,要一直執行一個 dos 視窗看起來確實挺扯的,初學 python 可能好多東西還是不懂,因此為了讓這個需求更優雅一些,我拿起了 C++ ,我們還是先來寫一個服務吧
2、 C++ 實現
為了實現這個需求,我也真是拼了
a、 C++ 服務
不得不說, C++ 寫服務還是挺費勁的,在網上扒了一個服務的模子,我便開始寫了,其實最主要的還是要實現服務中的死迴圈函式,程式碼邏輯和上述 python 的思路一起,區別就是我們需要使用 C++ 的語法實現一遍而已。
POINT p; GetCursorPos(&p);//獲取滑鼠座標 int x = p.x, y = p.y;//返回滑鼠的座標 time_t now_time = time(NULL); if (x == oldx && y == oldy) { int stay_seconds = int(now_time - prev_time); if (stay_seconds >= 6) { prev_time = now_time; SetCursorPos(x + 10, y + 10); SetCursorPos(x, y); WriteToLog("模擬一次滑鼠移動"); mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, x, y, 0, 0); WriteToLog("模擬滑鼠單擊"); keybd_event('esc', 0, 0, 0); keybd_event('a', 0, 0, 0); WriteToLog("模擬點選esc"); } } else { //更新舊座標 最後一次移動滑鼠時間 oldx = x; oldy = y; prev_time = now_time; }
接下來的操作就是我們需要把寫好的服務安裝並啟動
1. sc create test binPath= 可執行檔案的路徑 2. net start test 3. net stop test 4. net delete test
執行上述步驟1和2即可啟動服務
經過測試,太不幸了, GetCursorPos(&p) 返回的座標也為(0, 0),這下真是鬱悶了,服務這條路難道真的走不通了嗎?看到的大神有解決思路的還請在評論區支出,不勝感激。。。
b、 C++ 可執行程式
照搬照抄上述 python 服務轉可執行程式的邏輯,我們把 C++ 服務裡的程式碼拿出來,放到 C++ 可執行程式中,我們也可以實現一個 C++ 可執行程式
進過測試,以上 python 程式和 C++ 程式都還有一些問題,在一些特定的視窗上模擬滑鼠、鍵盤操作不好使,比如 notepad ++ 、windows工作管理員等,系統在5分鐘後還是鎖屏,思前想後,覺著這個可能和程式許可權有關係, 隨即把 C++ 工程的屬性進行了調整,生成的exe需要帶有管理員許可權,在次進行測試,結果是完美的 ,我們終於可以防止系統自動鎖屏了,執行上述python的程式這裡就不做許可權升級研究了,有興趣的同學自行研究
c++ 程式我們可以通過設定來吧程式設定成後臺執行的,沒有任何介面,這樣顯得更優雅一些,首先我們建立的是一個dos程式,設定設定兩個地方即可
- 聯結器->系統:子系統設定成視窗 (/SUBSYSTEM:WINDOWS)
- 聯結器->高階:入口點設定成mainCRTStartup
三、優化
最開始的模擬使用者操作,我們使用的是點選滑鼠左鍵、移動滑鼠、和模擬點選esc按鍵,但是上述操作都會不懂程度的帶來一些影響。
例如:
- 如果使用者正在看視訊,沒有進行鍵鼠操作,這個時候如果點選滑鼠左鍵,可能會導致視訊暫停,不是使用者期望的操作
- 如果使用者打開了一個彈框,例如頂層視窗是一個esc快捷鍵可以關閉的程式,這個時候如果使用者2分鐘沒有操作電腦,那麼模擬的esc按鍵將會把使用者的原始狀態打亂
模擬操作優化過程
1、win+d
切換到桌面,隨即在開切換回來,這個操作相對來說比較靠譜,但是如果有一個視窗上有模態視窗存在,也會對打亂原始的視窗順序
2、win鍵
點選 windows 鍵,隨即在點選一次,恢復到點選之前的狀態,這個操作相對第一種還是比較友好的
3、切換大小寫
點選CapsLk按鍵,進行大小寫切換,由於這個時候使用者沒有操作電腦,因此切換大小寫不會對使用者操作進行干擾,而且動靜更小、更優雅
上述3中模擬操作行為基本思路都是一樣的,只是模擬的方式所有不同,下邊我們就以第三種方式講解實現過程
點選CapsLk的操作分兩部分,第一次主要是為了模擬使用者點選,第二次是為了恢復第一次操作留下的痕跡,為了讓程式更優雅的執行,我們這裡需要啟動子執行緒來恢復主執行緒留下的痕跡
main函式程式碼如下
hMutex = CreateMutex(NULL, FALSE, (LPCWSTR)"PreventLockScreenApp"); WaitForSingleObject(hMutex, INFINITE); HANDLE hThread = CreateThread(NULL, 0, RestoreWinState, NULL, 0, NULL); //建立執行緒01 CloseHandle(hThread); //關閉控制代碼 remove(LOGFILE); time_t prev_time = time(NULL);// int oldx = 0; int oldy = 0; char positionText[100] = { 0 }; sprintf(positionText, "防鎖屏程序已啟動,程式將在無滑鼠移動情況下每隔%d秒模擬一次鍵盤操作", Job_TIME); WriteToLog(positionText); while (1) { POINT p; GetCursorPos(&p);//獲取滑鼠座標 int x = p.x, y = p.y;//返回滑鼠的座標 time_t now_time = time(NULL); if (x == oldx && y == oldy) { int stay_seconds = int(now_time - prev_time); if (stay_seconds >= Job_TIME) { prev_time = now_time; keybd_event(VK_CAPITAL, (BYTE)0, 0, 0); keybd_event(VK_CAPITAL, (BYTE)0, KEYEVENTF_KEYUP, 0); WriteToLog("模擬點選CapsLk,切換大小寫"); //釋放鎖 讓子執行緒去恢復win鍵狀態 ReleaseMutex(hMutex); Sleep(Restore_TIME / 2); WaitForSingleObject(hMutex, INFINITE); } } else { //更新舊座標 最後一次移動滑鼠時間 oldx = x; oldy = y; prev_time = now_time; } int stay_seconds = int(now_time - prev_time); char mousemoved[100] = { 0 }; sprintf(mousemoved, "滑鼠%d秒未移動", stay_seconds); WriteToLog(mousemoved);//列印座標 char positionText[100] = { 0 }; sprintf(positionText, "當前滑鼠位置(%d,%d)", x, y); WriteToLog(positionText);//列印座標 Sleep(SLEEP_TIME); }
子執行緒程式碼如下
void SimulationBehavior() { keybd_event(VK_CAPITAL, (BYTE)0, 0, 0); keybd_event(VK_CAPITAL, (BYTE)0, KEYEVENTF_KEYUP, 0); } DWORD WINAPI RestoreWinState(LPVOID lvParamter) { while (true) { WaitForSingleObject(hMutex, INFINITE); Sleep(Restore_TIME); SimulationBehavior(); ReleaseMutex(hMutex); } return 0; }
主子執行緒使用一個全域性的訊號量來進行同步
四、demo下載
需要 C++ 和 python 程式碼的同學到 csdn 下載: C++實現的防鎖屏後臺程序-內含python實現程式碼
進群:548377875 即可獲取數十套PDF!