小編心語:锵锵锵!小編我又來了!昨天發了一篇比較實用的《Python聊天室》,鑒于反響還不錯,SO ,小編也想給大家多分享點有用的幹貨,讓大家邊學邊用。好了,閑話不多說,今天要給各位看官們介紹的是基于C語言的帶提醒功能計時器。
你還在為錯過重要的事情而心煩不已嗎?
你還在為沒搶到某米手機而扼腕歎息嗎?
你還在為沒領到美食免單而空遺口水嗎?
從今天起,這些都不是問題,且看小編我給你一一道來!
咳咳咳~上菜啦~
dialog+ncurse實作指令行帶提醒功能計時器
我們将那個結合shell程式設計與C語言程式設計使用dialog工具與ncurses庫實作一個仿tmux時間風格的帶提醒功能的計時器
一、說明
這次項目課要實作的東西功能很簡單,但是卻用到了好幾個東西,包括
dialog
,
ncurses
庫,
zenity
,
moc
, 用了這麼多東西最後也就是個定時器-_-||。不過我想先告訴你的是為什麼是它。這原因呢有兩個,一個是之前看到有使用者反應希望項目課有一些綜合性稍強的 内容,這個項目課就是了,結合了shell程式設計和C語言程式設計,C語言程式設計除了包含本身的内容還涉及到了如何使用外部庫來實作我們想要的功能;這第二個原因 是,我自己需要一個這樣的定時器啊,我要每周二中午12點的時候準時搶小米手機(搶了幾次沒搶到啊,就是因為時間沒掐準,不夠快),是以就想要一個定時器 了,我想的是除了要實作基本功能外還要求“看起來,高大上”。
二、功能介紹
既然我是要實作一個指令行的定時器,要想“高大上”,那就必須要用到指令行的圖形庫了,一開始想到的是ncurses,這就得完全用C語言來開發了,後來又想到可以用shell下的dialog(基于ncurses實作的指令行圖形對話框,包含一些常用的構件)來完成一部分功能,比如設定時間的界面可以用
dialog
的
timebox
構件來實作,如下圖
本想是不是可以用dislog實作全部我想要的效果,比如在設定時間之後,進入倒計時界面,類似下面的效果
這實際是tmux内建時鐘效果。然後翻翻dialog的文檔,發現連字型大小都沒法設定,也無法設定構件内文本的顯示位置,那就沒辦法了,不過 dislog底層就是基于ncurses來實作的,我也就用ncurses來實作倒計時顯示的這個功能吧。最後實作的效果如下,是不是山寨得跟原版 (tmux, ctrl+b t)一樣。
這樣就滿足了嘛,當然不,我們還要再加點東西,讓它像鬧鐘一樣時間到放歌音樂如何,或者再彈個窗,免得我埋頭工作忘了"正事"(搶手機),播放音樂我用moc(一個流行的指令行音樂播放器),彈窗用zentiy(基于gtk構件的簡單彈出式對話框)
功能介紹完了,下面就讓我們來實作吧
三、具體實作
1.使用dialog設定時間
這個功能在shell腳本中實作
我們建立了一個函數
function SetTime()
{
local c_hour=`date +%H`
local c_minute=`date +%M`
local c_time=`date +%H:%M:%S`
s_time=`dialog --stdout --title "Set Time" --time-format %H\:%M\:%S --timebox\
"Current time is\n$c_time\nPlease set the deadtime\n" 0 0 $c_hour $c_minute 00`
}
上面代碼中再
SetTime
函數内部定義了三個局部變量,用于通過
date
指令分别擷取系統目前時間的時、分,并完整時間顯示為構件的資訊, 這裡
dialog
的第一參數
--stdout
指定繪制視窗的裝置,
--title
指定構件的标題,
--time-format %H\:%M\:%S
,用于指定設定完成後
dialog
傳回的時間格式,
--timebox
就指定了目前
dialog
構件的類型(因為dialog支援多種構件類型,是以這裡必須指定)。最後5個參數
0 0 $c_hour $c_minute 00
,分别以空格區分開,表示構件的寬度,高度,小時,分鐘,秒,具體請參看
dialog
man
文檔。完成後你可以按鍵盤的
Tab
鍵切換選中項,然後移到你要修改的時間上,使用鍵盤的上下鍵更改數值。
我們首先用shell實作的功能就這些,暫時把它放下,我們再來實作用C語言實作的功能。
2.使用ncurses庫實作倒計時顯示
關于ncurses庫的使用,如果你完成過實驗樓上面的另一個項目課C語言貪吃蛇,那麼你應該比較熟悉它的使用了,因為這個項目課不是專門介紹ncuses的,是以這裡也不會用更多其它關于ncurses的内容了,當然除了我們用到的。
實作顯示大号字型(數字)如前面的效果圖,因為ncurese本身是沒有關于字型設定的實作的,是以這需要我們自己來繪制,我們考慮可以為每個數字 建立一個基于父窗體口的子視窗,然後在每個視窗中繪制一個數字,要繪制數字,我們還需要自定義一組字模(隻包含10個數字和一個冒号)。這裡我們一共需要 8個子窗體,下面這個函數講建立多個子窗體
void create_clock_subwindow(WINDOW *win)
{
int top_win_width, top_win_height;
int starty, startx;
int i;
// 擷取父視窗的高度好寬度
getmaxyx(win, top_win_height, top_win_width);
if (top_win_height < D_HEIGHT || top_win_width < D_WIDTH){
printf("window is too small, please relize the window\n");
return;
}
// 用于訓示建立的子窗體繪制的起始位置
starty = (top_win_height - D_HEIGHT) / 2;
startx = (top_win_width - D_WIDTH*8-7) / 2;
for(i = 0; i < 8; i++){
digit_win[i] = subwin(win, D_HEIGHT, D_WIDTH, starty, startx+i*(D_WIDTH+1));
}
}
八個子窗體都是基于主窗體的相對位置建立的,以便适應你重新更改終端的大小,這其中
digit_win
是預先聲明的一個全局數組,
D_HEIGHT
D_WIDTH
,為預先定義的兩個訓示每個數字的寬和高的宏定義
WINDOW *digit_win[8];
相信你更關心的是如何定義一個字模,呵呵,自己在紙上畫好一個5*5的格子,然後畫上數字,像你小時候看過的電子表上的數字那樣畫(點陣字型),然後被覆寫的格子的位置填上
1
,否則為
,然後基于這11個格子建立一個三維數組就像下面這樣:
char digit[11][5][5] = {
{ { 1,1,1,1,1 }, /* 0 */
{ 1,0,0,0,1 },
{ 1,0,0,0,1 },
{ 1,0,0,0,1 },
{ 1,1,1,1,1 } },
{ { 0,0,0,0,1 }, /* 1 */
{ 0,0,0,0,1 },
{ 0,0,0,0,1 },
{ 0,0,0,0,1 },
{ 0,0,0,0,1 } },
{ { 1,1,1,1,1 }, /* 2 */
{ 0,0,0,0,1 },
{ 1,1,1,1,1 },
{ 1,0,0,0,0 },
{ 1,1,1,1,1 } },
{ { 1,1,1,1,1 }, /* 3 */
{ 0,0,0,0,1 },
{ 1,1,1,1,1 },
{ 0,0,0,0,1 },
{ 1,1,1,1,1 } },
{ { 1,0,0,0,1 }, /* 4 */
{ 1,0,0,0,1 },
{ 1,1,1,1,1 },
{ 0,0,0,0,1 },
{ 0,0,0,0,1 } },
{ { 1,1,1,1,1 }, /* 5 */
{ 1,0,0,0,0 },
{ 1,1,1,1,1 },
{ 0,0,0,0,1 },
{ 1,1,1,1,1 } },
{ { 1,1,1,1,1 }, /* 6 */
{ 1,0,0,0,0 },
{ 1,1,1,1,1 },
{ 1,0,0,0,1 },
{ 1,1,1,1,1 } },
{ { 1,1,1,1,1 }, /* 7 */
{ 0,0,0,0,1 },
{ 0,0,0,0,1 },
{ 0,0,0,0,1 },
{ 0,0,0,0,1 } },
{ { 1,1,1,1,1 }, /* 8 */
{ 1,0,0,0,1 },
{ 1,1,1,1,1 },
{ 1,0,0,0,1 },
{ 1,1,1,1,1 } },
{ { 1,1,1,1,1 }, /* 9 */
{ 1,0,0,0,1 },
{ 1,1,1,1,1 },
{ 0,0,0,0,1 },
{ 1,1,1,1,1 } },
{ { 0,0,0,0,0 }, /* : */
{ 0,0,1,0,0 },
{ 0,0,0,0,0 },
{ 0,0,1,0,0 },
{ 0,0,0,0,0 } }
};
void display_a_digit(WINDOW *win, char digit[5][5], char isnear)
{
int y, x;
int color;
color = isnear?COLOR_PAIR(4):COLOR_PAIR(3);
werase(win); // erase the window before re-put the number
for(y = 0; y < 5; y++){
for(x = 0; x < 5; x++){
if (digit[y][x] == 1){
wattron(win, color);
mvwprintw(win, y, x, "a");
wattroff(win, color);
}
}
}
}
這樣我們邊實作了可能最讓你覺得困難的一部分代碼
再看主程式
int main(int argc, char *argv[])
{
char isnear;
int screen_width, screen_height;
int i;
int hour_tens, hour_units, min_tens, min_units, sec_tens, sec_units;
time_t current_time;
long left_time, end_seconds;
char *str;
struct tm *c_time;
WINDOW *win1;
// 為存放提醒資訊的字元串配置設定記憶體
str = malloc(256*sizeof(char));
memset(str, '\0', sizeof(char)*256);
// 判斷參數輸入參數個數是否正确
if (argc < 2 || argc > 4){
printf("Usage: %s [options] <seconds> [info strings]\n", argv[0]);
return 0;
}
if (strcmp(argv[1], "-c")){
// 将傳遞進的計時時間字元串(以秒為機關的時間)轉換為lang型
left_time = atol(argv[1]);
if(argc == 3)
strcpy(str, argv[2]);
}else if(!strcmp(argv[1], "-c")){
// 将傳遞進的計時時間字元串(以秒為機關的時間)轉換為lan
left_time = atol(argv[2]);
if (argc == 4)
strcpy(str, argv[3]);
}else{
printf("Usage: %s [options] <seconds> [info strings]\n", argv[0]);
return 0;
}
// 初始化ncurses
initscr();
cbreak();
noecho();
// 自定義顔色
start_color();
init_color(COLOR_BLACK, 46, 52, 54);
init_color(COLOR_BLUE, 52, 101, 164);
init_color(COLOR_WHITE, 128, 128, 128);
init_pair(1, COLOR_BLACK, COLOR_BLACK); // use for main win
init_pair(2, COLOR_BLUE, COLOR_BLACK); // use for clock win
init_pair(3, COLOR_BLUE, COLOR_BLUE); // use for clock digit
init_pair(4, COLOR_RED, COLOR_RED); // use for clock digit
init_pair(5, COLOR_WHITE, COLOR_BLACK); // use for clock digit
// 建立病初始化住視窗
getmaxyx(stdscr, screen_height, screen_width);
win1 = newwin(screen_height, screen_width, 0, 0);
// 設定視窗背景顔色
wbkgd(win1, COLOR_PAIR(1));
// 擷取系統時間,并計算出計時終止時間
time(¤t_time);
end_seconds = current_time + left_time;
// 進入主循環,開始計時
while (left_time){
// 休眠一秒鐘,減少cpu占用時間
sleep(1);
time(¤t_time);
left_time = end_seconds - current_time;
// 設定接近計時結束的标志,這裡設定的為120s,即分鐘,用于當計時隻有兩分鐘時講數字顔色換成紅色
isnear = (left_time < 120)?TRUE:FALSE;
// 當傳入"-c"參數時,使用正常顯示時間模式
if (strcmp(argv[1], "-c")){
hour_tens = left_time/3600/10;
hour_units = left_time/3600%10;
min_tens = left_time/60%60/10;
min_units = left_time/60%60%10;
sec_tens = left_time%60/10;
sec_units = left_time%60%10;
}else{ // 使用倒計時顯示模式
c_time = localtime(¤t_time);
hour_tens = c_time->tm_hour/10;
hour_units = c_time->tm_hour%10;
min_tens = c_time->tm_min/10;
min_units = c_time->tm_min%10;
sec_tens = c_time->tm_sec/10;
sec_units = c_time->tm_sec%10;
}
// 更新數值前,清除主視窗顯示
werase(win1);
// 每次建立新的子窗體,以便适應終端的大小調整
create_info_subwindow(win1); // info window
// 設定提醒資訊顯示的字型屬性
wattron(info_win, COLOR_PAIR(5));
mvwprintw(info_win, 0, 0, "%s", str);
wattron(info_win, COLOR_PAIR(5));
// 建立計時數字子窗體
create_clock_subwindow(win1);
// 分别設定子窗體背景顔色
for(i = 0; i < 8; i++){
wbkgd(digit_win[i], COLOR_PAIR(2));
}
// 講相應時間的數字繪制到對應視窗中
display_a_digit(digit_win[0], digit[hour_tens], isnear);
display_a_digit(digit_win[1], digit[hour_units], isnear);
display_a_digit(digit_win[2], digit[10], isnear);
display_a_digit(digit_win[3], digit[min_tens], isnear);
display_a_digit(digit_win[4], digit[min_units], isnear);
display_a_digit(digit_win[5], digit[10], isnear);
display_a_digit(digit_win[6], digit[sec_tens], isnear);
display_a_digit(digit_win[7], digit[sec_units], isnear);
// 放置子窗體在主窗體中
touchwin(win1);
// 重新整理窗體
wrefresh(win1);
}
free(str);
// 銷毀所有窗體,回收系統資源
destory_all_win(win1);
endwin();
return 0;
主程式最開始完成一些初始化工作,然後在一個主循環完成了主要的倒計時功能。
使用C語言通過ncurses庫實作倒計時的功能完成了,我們再回到shell中去
3.在shell腳本中調用倒計時程式
小編我來說一句,這段小小的代碼相信難不倒大家,小編也就不在這裡多做叙述了,當然,如果有看不懂得地方歡迎登陸實驗樓官方網站http://www.shiyanlou.com/courses/?course_type=project&tag=all
裡面有更多詳細的内容~
小編先行告退,各位看官請繼續~
4.計時完成後,播放音樂和彈窗提醒
if [ $? -eq 0 ];then
if [ -n "$music" ];then
mocp -S $music
mocp -p
fi
zenity --info --width=100 --text=$info
if [ $? -eq 0 ];then
_exit
fi
fi
注意這段代碼,應緊接着上面那段代碼的後面,因為這裡用到了
$?
,你如果學過shell程式設計,應該能明白這是為什麼,
$?
表示上一個程式的傳回值。我們這裡判斷它是否的等于0,表示上面的計時程式是否計時結束,如果結束正常傳回0,就表示該開始提醒你了。 播放音樂就用到了那個mocp程式,你如果不熟悉它的話,可以先安裝好這個程式,并在指令行單獨運作它來熟悉它的使用,遇到任何困難,最好的解決方法就是使用
--help
參數和
man
文檔。
$ sudo apt-get install moc
上面
mocp
-S
的參數表示以服務的形式運作程式,
-p
為play,
-x
為退出服務,當然也會停止播放目前音樂,我們使用單獨的函數完成程式退操作
function _exit()
{
if [ -n "$music" ];then
mocp -x
fi
clear
exit 0
}
上面的
zenity
即為一個基于gtk的彈出式對話框程式,後多種構件樣式可選,我們這裡使用的是
--info
構件
5.程式參數處理
如你預見的一樣,這個程式多種參數,如果處理不好可能出現不必要的麻煩,是以這裡單獨說明一下這個程式是如何處理參數傳遞的。這裡采用了一個很通用的方法,使用
shift
指令移位傳遞參數
while [ -n "$1" ];do
case "$1" in
-c) option="-c" ;;
-m) music="$2" ;;
-i) info="$2" ;;
*) show_erro ;;
esac
if [ "$1" == "-c" ];then
shift
else
shift 2
fi
done
從上面你可以看到,對
-c
參數的處理有點不一樣,因為其他幾個參數都會有一個值對應,而
-c
參數沒有,是以它的移位操作隻移動了一個位置。現在我們可以使用如下的方式傳遞參數
$ ./countdown -i "hello shiyanlou" -m music.mp3
$ ./countdown -c -i "hello shiyanlou" -m music.mp3
$ ./countdown -i "hello shiyanlou"
$ ./countdown
至此主要的一些麻煩的問題我們都解決了,你在熟悉了上面的實作過程之後,可以參考以下完成代碼(小編在這裡插播一句,下面的是需要在我們實驗樓網站的虛拟平台下才能實作的,不過看到這裡,大家應該對于這個程式有了大緻的認識,自己稍作修改就可完成屬于自己的一個帶提醒功能的計時器)
$ git clone https://github.com/shiyanlou/countdown.git
C程式使用下面的指令生成
$ gcc disclock.c -o disclock -lncurses
你可能需要安裝如下程式
$ sudo apt-get install libncurses5-dev moc dialog zenity
感謝各位看官的支援~小編以後盡量會在每個工作日,來與大家分享小編整理的幹貨,小編會整理一些自己覺得比較實用的程式分享給大家,當然如果大家有需求也可以私信我,或者也可以登陸實驗樓的課程版塊http://www.shiyanlou.com/courses/?course_type=all,如果這些還滿足不了看官您的需求,可以登入實驗樓的分享版塊http://forum.shiyanlou.com/forum.php?mod=guide&view=newthread,這裡有在實驗樓學習的孩紙們分享的幹貨和學習心得,最後小編祝大家在碼農的康莊大道上一帆風順!