pretty code

顯示具有 程式 標籤的文章。 顯示所有文章
顯示具有 程式 標籤的文章。 顯示所有文章

2021年6月24日 星期四

Hide/Show Desktop Icons on Windows 10 by C code

雖然我們在桌面按下滑鼠右鍵,就可以在檢視功能的子選單中選擇是否要顯示桌面圖示,但需使用滑鼠又要兩個操作步驟感覺就很不好用。

昨天晚上終於狠下心來把它完成,雖然我之前已經 Google 過了,但肩膀受傷後人就變得很懶,除了工作以外的程式都很懶得動工,這個星期老婆進公司上班故不用張羅三餐,我連自己的午餐都隨便吃,可能是偷懶的緣故,感覺這星期肩膀狀況還不錯,所以昨天才會想來做這件事。

好久沒寫 Windows 相關的程式了,很多 HWND 等的參數都變得很陌生。

一開始在沒 Google 前,直覺就是可能跟 registry 有關,也找到相關的 registry。

[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced]
"HideIcons"=dword:00000001

原本以為只要修改這個值後,再想辦法用廣播方式送出 WM_SETTINGCHANGE 訊息,應該就可以生效,但據我實驗的結果,桌面程式知道我動了 registry,但並不會生效,我猜它應該只是讀 registry 得知目前的值,才決定是否要在 "顯示桌面圖示" 前打勾。

好吧,只好走 Google 來的方式,來解決這個問題。


要特別注意的一點,上面有說應該要看 OS 版本來決定對誰發送訊息,但我在寫 Code 前有先用 Spy 這類的軟體來找尋視窗,發現在 Windows 10 跟連結的說法不太一樣。

順序應該是如下:

01. 找到 Progman 這個 Window。
02. 找到 class 為 "SHELLDLL_DefView" 的視窗。
03. 送出 WM_COMMAND 訊息。


int toggleIconByShell() {
    int res = 0;

    HWND progman;
    HWND defView;

    // Find SHELLDLL_DefView class
    progman = FindWindowExA(NULL, NULL, "Progman", "Program Manager");
    printf("Program is 0x%p\n", progman);

    defView = FindWindowExA(progman, NULL, "SHELLDLL_DefView", "");
    printf("SHELLDLL_DefView is 0x%p\n", defView);


    // Send message to defView
    int lParam = 0;
    int wParam = 0x7402;

    LSTATUS lResult = SendMessage(defView, WM_COMMAND, wParam, lParam);

    if (lResult != ERROR_SUCCESS) {
        printErrorCode(lResult);
        res = lResult;
    }

    printf("OK\n");
    return res;
}

2021/06/24 更新

又把文章仔細看了一遍,SHELLDLL_DefView 在某些情況下,會從原本的 Progman 跑到 WorkerW 下面,故需要針對不同情況去找 Handle,將尋找 Handle 獨立出來變成函數,增加可讀性。


#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

void printErrorCode(LSTATUS lResult)
{
    printf("ErrorCode = %d(0x%08X) \n", (int)lResult, (int)lResult);
}

BOOL EnumCallback(HWND hwnd, LPARAM lParam)
{
    char name[256];
    memset(name, 0x00, sizeof(name));

    GetClassNameA(hwnd, name, sizeof(name));

    if (strcmp(name, "WorkerW") == 0) {
        HWND *address = (HWND*)lParam;

        HWND defView = FindWindowExA(hwnd, NULL, "SHELLDLL_DefView", "");

        if (defView != NULL) {
            *address = defView;
            return FALSE;
        }
    }
    return TRUE;
}

HWND getDefViewHandle()
{
    HWND progman;
    HWND defView;

    // Find SHELLDLL_DefView class
    progman = FindWindowExA(NULL, NULL, "Progman", "Program Manager");
    printf("Program is 0x%p\n", progman);

    defView = FindWindowExA(progman, NULL, "SHELLDLL_DefView", "");
    printf("SHELLDLL_DefView is 0x%p\n", defView);

    if (defView == NULL) {
        HWND *address = &defView;
        // defView is moved to "WorkerW"
        EnumWindows(EnumCallback, (LPARAM)address);
        printf("After searching WorkerW, SHELLDLL_DefView is 0x%p\n", *address);
        defView = *address;

        // SHELLDLL_DefView is not the child of WorkerW.
        if (defView == NULL) {
            HWND desktop = GetShellWindow();
            defView = FindWindowExA(desktop, NULL, "SHELLDLL_DefView", "");
            printf("After searching desktop, SHELLDLL_DefView is 0x%p\n", defView);
        }
    }

    return defView;
}

int toggleIconByShell()
{
    int res = 0;

    HWND defView = getDefViewHandle();

    if (defView == NULL) {
        printf("SHELLDLL_DefView not found \n");
        return -1;
    }

    // Send message to defView
    int lParam = 0;
    int wParam = 0x7402;

    LSTATUS lResult = SendMessage(defView, WM_COMMAND, wParam, lParam);

    if (lResult != ERROR_SUCCESS) {
        printErrorCode(lResult);
        res = lResult;
    }

    printf("OK\n");
    return res;
}

int main(void) {
    toggleIconByShell();

    system("pause");
    return 0;
}

2021/06/25 更新

在已經知道訣竅的情況下,抓取訊息就變得很簡單,但當初怎樣找到標的物和要送的訊息,我想這才是最困難的部份吧。

2021年5月1日 星期六

安西教練,我好想寫程式呀…

肩膀受傷已經 3 個禮拜了,感覺像個廢人一樣,每當工作上有想法想寫個小工具幫忙減輕工作負擔,往往也只能對著鍵盤嘆息,實在是沒辦法用左手的一根手指頭打出一行又一行的程式碼!手機的發文還可以透過語音轉文字搞定,但程式我真的是無轍,我總不能對著輸入法說:int main(void) 大括號吧?

今天無聊又在亂轉 Youtube 時,無意間得知一個約 20 年前很紅的團體 — IPIS 蟑螂合唱團的近況,內心不禁感嘆,華語音樂圈真的是沒落得好快,我想他們也會覺得真的是見證時代的眼淚呀!

個人認為下一個產業應該就是圖書出版產業相關的,尤其看到電子書發展的那種鳥樣,只有一個又一個不爭氣的廠商!已經沒有多少人要看書了,大家還不一起團結合作力挽狂瀾,我想下場是可想而知的,就連我這個每年花四萬買電子書的人都快要開始對電子書失去興趣了,人畢竟一天只有 24 小時可用,這兩年多為了看繁體中文書,壓縮了我看程式書籍的時間,導致我的程式功力都停滯不前,是該漸漸放手讓它走了,畢竟我到現在連一台台灣廠商的電子紙手機都還等不到,我已經沒有耐心不想再等了。

話題好像扯遠了,我發現 IPIS 蟑螂合唱團有兩首歌特別適合寫程式的時候聽:
1. Call Me Call Me。
2. 幫個忙。

希望等我開始可以寫程式的時候,有了這兩首神曲可以幫忙我追上 OKR 的進度!

Q1 領先進度的紅利就這樣硬生生被肩膀受傷吃掉,真的是人算不如天算呀XD

2020年10月15日 星期四

小程式也要講究效率

自從不用 C 寫 Parser 後,我已經習慣用 Re 來處理 Log,雖然明知道每次重覆在同一份 Log 中找尋不同資料是很愚蠢的事,應該只 parsing 一次後就把資料儲存起來!但自己寫的小程式自己用倒也還好,短短幾行 Code 看起來也比較舒服XD

後來另外一個主管看到這樣的小程式在做實驗時幫助判斷問題很方便,就延用了我的小程式做他自己想做的實驗,一直以來倒也相安無事。

直到有一天,因為他做了一些變化,導致 Log 容量膨脹許多,故開始想要節省時間,於是請我幫忙改進,我原本以為純粹是後續報表畫圖很花時間,即使我做了改進,但時間可能也節省不到 20%?不過不改不知道,單純測試 103 個單位,便少了 93% 的時間,改進後沒有再接到需求應該是有達到目標?因為不是我要用的,我也沒有繼續追問下去XD

最近都在搞 ESP8266,我發現這樣下去不行?還是來發篇文章灌個水,順便解答我心中的疑惑。

就以我原本的小程式來測試改進幅度!

改進前:13 分 33 秒。

改進後:03 分 11 秒。

計算之下,少了 76 % 的時間。

再繼續細分下去,一次的 parsing 動作可以節省 4.2 ms,只要執行次數夠多,花費時間還是很可觀的。

難怪古人說:勿以善小而不為,勿以惡小而為之。戒之戒之。

2019年11月18日 星期一

傳說中的記憶體增加警告

之前為了讓網域上的 device 可以連接 Server 做測試,需要有合法的 OS,但 IT 只有 Win10 的授權可以安裝,反正也不需要什麼多好的效能,故上線時倒也一直相安無事。

約莫是一年前發現 Server 會無緣無故無法從遠端連線,即使人在本機前面,輸入帳密後依然無法操作電腦,經過了幾次觀察後才發現,是因為實體記憶體已經快耗盡,系統不斷在做 Swap 才導致操作無法回應。後來歸納出是因為 Win10 只要一陣子沒有做 Windows Update,記憶體便會緩慢的不斷增加,而我們的電腦只有 8G 的 RAM 可用。

手上 Coding 的工作便做不完了,實在是不想每天做 IT 營運檢查,故那時寫了一個簡單程式,會去監控 Server 的記憶體使用情形,每天會發送一次 mail 報告目前記憶體使用情形,每一個小時也會檢查一次,如果超過 50% 便會再發一次 mail 通知,覺得記憶體使用過多,便遠端去做 Windows Update 即可。

雖然針對電腦管理也有很多免費軟體可用,但實在是不想安裝軟體,故自己來還是比較簡單,對 Server 也比較沒負擔,因為管理軟體大概都需要開啟 Web 服務以及安裝資料庫,好方便監控。

不知不覺就這樣過了一年,昨天休假時無聊開啟公司信箱,第一次遇到超過 50% 的情形,截張圖紀念一下。

話說 Win10 還真吃記憶體,昨天每日報告還在 38% 左右,晚上 5 點多左右便升到了 55%,Windows 還真是讓人猜不透呀。

2018年12月11日 星期二

GoBus

程式碼

剛來這家公司時
為了早日了解專案順便吃便當
常常看 code 看到忘了時間
於是便有了這支小程式的誕生

此程式的原理是去 parse 公車網站
並顯示出還有多久公車會來
程式寫好後,便設定工作排程器固定時間執行
時間一到程式會顯示在右下角
這樣一來就不會忘記回家了

因為只是支小程式
故並未使用 Thread 只是偷懶使用 Timer
當遇到網路連線太久時,UI 便會無回應(顯示空白)
不過目前夠用就好

2017年9月12日 星期二

happy-go-lucky 計算下班時間

以後不知道要寫什麼文章
就來寫些小程式誕生的故事

今天第一個故事就是計算下班時間的小工具

雖然說身為 RD 的我們不太可能準時下班
但偶爾也會有早來想要早點下班運動的時候

很不巧的我們的刷卡記錄要明天才能查詢
雖然我們可以偶爾不做滿 8個小時
但奉公守法的我還是覺得很不習慣

於是就想要知道大概的上班時間
這樣才能確定要幾點才能下班

後來靈機一動
想要利用電腦開機的時間來當做刷卡時間
理論上刷卡時間一定會在電腦開機之前
故以這個時間當基準一定不會有錯

註:不管是什麼機器,時間都需要校時
以我以前 MIS 的經驗,刷卡機會跟資料庫主機對時
而資料庫主機又會跟 AD Server 對時
AD Server 又會跟外部服務對時
這樣才能確保大家的時間都一致

以 Windows來說
我們可以去 事件檢視器 -> Windows 記錄 -> 系統
尋找一個事件檢視碼為 12 的事件
裡面就會有我們開機的時間

這時候利用 Google 我們查到了 "wevtutil" 這個指令
它可以用來查詢相關的事件並指定 Filter 條件(時間)
由於這個指令會把所有事件都查詢出來
故我們再加上 Find 這個指令
只挑選出 EventID 12 的行數
wevtutil qe system  "/q:*[System[TimeCreated[@SystemTime>='2017-09-10T00:00:00']]]" | 
find /N "12</EventID>"

另外一種解法
EventID 12 有一個特殊欄位
只有 EventID 12 才有
wevtutil qe system /q:Event[EventData[Data[@Name='StartTime']>'2017-09-10T00:00:00']]

出來的結果如下
<Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'>
<System>
<Provider Name='Microsoft-Windows-Kernel-General' Guid='{A68CA8B7-004F-D7B6-A698-07E2DE0F1F5D}'/>
<EventID>12</EventID>
<Version>0</Version>
<Level>4</Level>
<Task>0</Task>
<Opcode>0</Opcode>
<Keywords>0x8000000000000000</Keywords>
<TimeCreated SystemTime='2017-09-12T01:30:41.749600400Z'/>
<EventRecordID>302816</EventRecordID>
<Correlation/>
<Execution ProcessID='4' ThreadID='8'/>
<Channel>System</Channel>
<Computer> xxxxx </Computer>
<Security UserID='S-1-5-18'/>
</System>
<EventData>
<Data Name='MajorVersion'>6</Data>
<Data Name='MinorVersion'>1</Data>
<Data Name='BuildVersion'>7601</Data>
<Data Name='QfeVersion'>23807</Data>
<Data Name='ServiceVersion'>1</Data>
<Data Name='BootMode'>0</Data>
<Data Name='StartTime'>2017-09-12T01:30:41.125599300Z</Data>
</EventData>
</Event>

好了,食材都有了,就讓我們開始下廚吧

我們利用 Golang os/exec, time, strings 等 package
就可以簡單炒出一盤好菜了

package main

import (
    "fmt"
    "log"
    "os/exec"
    "strings"
    "time"
)

const workTime = 8 * 60 + 75

func getCMDResult(today string) (string, error) {
    subcmd := fmt.Sprintf("wevtutil qe system /q:Event[EventData[Data[@Name='StartTime']^>'%s']]", today)
    cmd := exec.Command("cmd", "/C", subcmd)

    out, err := cmd.Output()
    if err != nil {
        return "", err
    }

    return string(out), nil
}

func getStartTime(log string) string {
    pattern := "'StartTime'>"

    i := strings.Index(log, pattern)
    if i >= 0 {
        start := i + len(pattern)
        out := log[start:start+30]
        return out
    }

    return ""
}

func getHumanTime(t time.Time) string {
    return fmt.Sprintf(
        "%04d-%02d-%02d %02d:%02d",
        t.Year(),
        t.Month(),
        t.Day(),
        t.Hour(),
        t.Minute(),
    )
}

func printWorkTime(startTime string) {
    t1, err := time.Parse(time.RFC3339, startTime)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println("")
    fmt.Printf("clock in  : %s \n", getHumanTime(t1.Local()))
    fmt.Printf("clock out : %s \n", getHumanTime(t1.Add(time.Duration(workTime)*time.Minute).Local()))
}

func main() {
    t1 := time.Now()
    t2 := time.Date(t1.Year(), t1.Month(), t1.Day(), 0, 0, 0, 0, time.FixedZone("UTC", 0))

    out, err := getCMDResult(t2.Format(time.RFC3339))
    if err != nil {
        log.Fatal(err)
    }

    startTime := getStartTime(out)

    if startTime != "" {
        printWorkTime(startTime)
        return
    }

    fmt.Println("Can't get start time in Windows event log")
}



https://github.com/tylpk1216/go-clockout

2017年1月5日 星期四

The comparison of Excel Librarys that written with different program language


最近幫人寫工具
遇到一些 Excel Library 相容性問題
故試了好幾種語言的 Excel Library
備忘一下

2014年3月21日 星期五

gnuplot 畫圖

這 2 天寫程式寫到有點煩。

突然想看看寫給其他部門程式的使用狀況,就寫了支程式把 MySQL 的 Log 抓回來分析,並以月為單位統計出查詢次數。

原本只想匯入 Excel 看看畫圖的結果,但是資料一變動就要重匯也是有點小笨。

以前都沒機會玩玩 gnuplot,不如就趁現在來 try try。

昨天還被參數搞到頭昏腦脹,今天不知道為什麼就開竅了?

有圖有真相,但這個系統看起來都沒有人用是怎樣!


2014年3月1日 星期六

daily money 搜尋 ready

感謝 Lancelot
直接用他改好的 Code
我的 daily money 終於可以 search 了

幸好一直有在 follow
才知道改了什麼
不過也是花了我半小時

下次可能就沒那麼幸運了
我的版本已經跟作者版和 Lancelot 版越差越多了

不會 java 改 Android 真是辛苦呀

2014年2月3日 星期一

SNMP 監控印表機

原本監控公司所有的印表機耗材使用情形是使用自己寫的程式。

原理是將每個印表機的耗材網頁抓回來,再去裡面 parsing 出我要的資料,不過遇到 EPSON 機器就沒輒,因為他使用 java applet,呈現出來的不是文字網頁,所以我也沒辦法 parsing。

之前有想過利用 SNMP 協定來跟 EPSON 機器溝通,但我一直以為 EPSON 是用 private 的 ID,故一開始方向就錯了,當然也不可能在網路上找到相關資料,理論上 private 也是要註冊,故找到管理組織應該也是可以?

最近剛好看到 1 個 PHP SNMP Library,試了一下還是無法抓取 EPSON 的資料,不過在研究 source code 的過程中,才發現原來是我誤會了,只要用 snmpwalk 的方式,就能順利抓到資料。

2013年7月24日 星期三

懶惰是進步的原動力

工作上偶爾要幫忙同事檢查機房,每天日記有一項是要記錄主機的硬碟剩餘空間,這部份已經寫好程式,有新的主機只要更改批次檔即可,但還需要手動將程式產生出來的結果打到 WORD。

這禮拜終於受不了了,本以為要寫點 WORD VBA,後來想到也許合併列印就可以解決我的問題,再加上來源檔可以選擇 CSV 檔,稍微修改程式和批次檔,所有的主機都不用自己打字了。

早就該這樣做了,以前的我不知在想什麼?

2012年6月8日 星期五

又是報表

感覺其他的程式語言要產生 EXCEL or WORD 檔案好像很容易,怎麼我會的程式語言就是那麼困難!

EXCEL 可以用 XML 格式解決,WORD 好像就只能用OLE,WORD 的 XML 格式好亂,真機車。

今天用 C 寫支小程式,可以將 XML 的 EXCEL 轉成一個 CPP 檔案,並能夠區分:
FontName
Worksheet
StringType
NumberType

以後只要格式做好,產出報表就快了。

2012年5月28日 星期一

EasyInstall

最近一直在換電腦,故已經重灌了好幾台不同年份的電腦,每次都要記住有那些軟體要灌,感覺很不 smart。

之前有找到一個軟體 InstallPad,號稱可以解決安裝軟體的問題,不過經實測透過網路磁碟機的方式實在是慢到爆,作者有提到架設 web server 的方式,但沒有那麼多美國時間去測試,所以只好又自己寫了。

想要的功能有:
1.提供軟體清單加速安裝過程,並以用途區分顏色。
2.安裝完軟體後有一個 checkbox 被 checked。
3.可以直接開啟安裝路徑的資料夾。
4.如果安裝的軟體需重開機,能記住這次所有的安裝狀態。
5.編輯軟體清單的功能。

明年如果不幸還在,重灌電腦一定可以飛快!

2011年10月22日 星期六

StarDict 字典檔格式

.ifo 
資訊檔,告知有多少字,索引檔大小等。
.idx 
索引檔,單字以'\0'結尾,4個byte(解釋位置),4個byte(解釋大小)。big-endian。
.dict
照索引檔的大小建立,放置音標,解釋等資訊。以utf8編碼,換行以0x0a,結尾不需0x00。
註:
如果字典檔要給 Android's ColorDict 用
單字一定要排序過,且第一個單字會找不到。

2011年6月21日 星期二

EasyMyProfile 補完

昨天幫同仁備份電子郵件時,才發現這個程式有個小缺點,當同仁檔案很多又很大時,你無法得知程式目前是否死掉,雖然我的程式不太可能當機,但這樣的使用者體驗肯定不好。

今天雖然心情很悲傷,還是稍微改了一下UI,利用 Timer 定時更新前一次的複製檔案名稱,我想這樣大概也差不多了吧。

2011年6月20日 星期一

EasyMyProfile

上個禮拜就在心情好與不好間,完成了這個一直想完成的程式,到確定備份成功前,也陸續修了幾個 BUG﹝如果把功能不對當BUG﹞,感覺自己好像也還寶刀未老,但自己很清楚的知道,再繼續做這個我不喜歡的工作,總有一天我一定會失去自己。

2011年6月16日 星期四

Windows批次檔 換行

雖然 BAT FILE 還蠻單純
如果想要美觀的話
適當的換行也是很重要滴
只要下 echo.
就等於 C語言的 printf("\n");

2011年6月6日 星期一

Android 抓取手機圖片

剛剛想要將昨天運動的 GPS Tracker 的記錄匯出來
但是 Google Earth 並無相關的統計資訊
(也許是我不會用?)

故決定來抓手機的圖片
google 了一下可以利用 Android SDK 來達成
無奈在我的電腦一直出現下列錯誤
Failed to get the adb version: Cannot run program "adb": CreateProcess error=2
似乎跟環境變數有關
只要執行指令
set path=%path%;E:\android-sdk-windows\tools;E:\android-sdk-windows\platform-tools\
接著執行 E:\android-sdk-windows\tools\ddms.bat
就可以順利的抓取圖片了

2011年6月1日 星期三

醉裡挑燈看Code

最近真的沒有寫程式的動力,不知道是不是因為心情不好?

想要寫一個備份使用者帳號資料的程式,前置工作都準備好了,包括登錄檔、實體檔案路徑等,雖然也不是多迂迴﹝大概要登錄檔查詢 2 次﹞,但搞懂後我也不想寫了XD

另外一個設定網路連線的程式也是,大概也要登錄檔查詢 2 次,才能得到正確的登錄檔設定 key 值。

好吧來寫一個最簡單的耗材報表程式﹝整合進 EasyReport﹞,想到要增加 UI 控制項也懶,最後先以 EXCEL VBA 解決每月報表表頭自動抓取月份的功能,剩下的剪貼簿 Parser 就看我心情吧,不然以我以前的個性,我一定會一頭鑽進 VBA 的世界。

今天倒也不是一無是處,為了解決繪圖機 com port 25 and 9 pin 的問題,拿起了三用電表量測腳位訊號,超懷念的,之前寫 driver 的日子好像又回來了。

我不要當公務員了...

2011年5月28日 星期六

自然人憑證上課延伸

我錯了
原來 OpenSSL 真的有包含 X.509
底下是個不錯的網站
http://www.imacat.idv.tw/tech/sslcerts.html.zh-tw#validity
以後有空可以參考

我覺得
不論是考試或是未來
這個是可以研究的課題

但是我數學很差呀XD