1. 程式人生 > >防止系統鎖屏-python、C++實現!

防止系統鎖屏-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程式,設定設定兩個地方即可

  1. 聯結器->系統:子系統設定成視窗 (/SUBSYSTEM:WINDOWS)
  2. 聯結器->高階:入口點設定成mainCRTStartup

三、優化

最開始的模擬使用者操作,我們使用的是點選滑鼠左鍵、移動滑鼠、和模擬點選esc按鍵,但是上述操作都會不懂程度的帶來一些影響。

例如:

  1. 如果使用者正在看視訊,沒有進行鍵鼠操作,這個時候如果點選滑鼠左鍵,可能會導致視訊暫停,不是使用者期望的操作
  2. 如果使用者打開了一個彈框,例如頂層視窗是一個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!

防止系統鎖屏-python、C++實現!