創作內容

8 GP

【程式】如何建一個視窗—X Window篇

作者:Shark│2017-10-20 04:01:46│巴幣:72│人氣:3485
用C/C++寫視窗程式的方法,上次介紹Windows API,這次介紹Windows以外的作業系統。

在類Unix作業系統裡,X Window是GUI的不成文標準,像Linux和BSD都是用它做出GUI,Linux、BSD版的GTK、QT是以它為基礎架出來的。它的歷史和架構在此不詳細介紹,本篇只介紹跟寫程式有關的部分。
有時也稱為Xlib、X11或X,Window是單數才是正式名稱,如果寫成「X Windows」是錯的。

本篇筆者用的OS是Linux Mint 18.2 64位元版。
寫本篇的程式要裝X Window開發用套件,筆者的發行版是libx-dev,其他發行版可能是不同的名稱。

(2021/3/30更新:改良語法標示,且發現另一個設視窗標題的方法)



X Window系統是由三個部分組成:server、client、window manager

server和window manager內建在作業系統,client是你寫的程式。server負責輸入和輸出,接收鍵盤和滑鼠事件以及把圖、文字顯示在螢幕。window manager是一個特別的client,控制視窗位置大小、重疊和邊框樣式,server根據window manager的計算顯示畫面。

重要的觀念是「X Window是以client、server和window manager可以不在同一台電腦上為前提設計」,這可以用在遠端操作的情況:client在一台沒有螢幕和輸入設備的大型電腦,用一台裝了server的PC連線去操作,但因此寫程式會有些限制。

三個部分是非同步執行(asynchronous),例如呼叫XMapWindow(),client只是發一個命令給server便繼續執行,不會等待map完成,如果map完成之前就送畫圖的指令那指令會無效。

還有視窗位置是由window manager管理,client沒有一個函式可以呼叫後直接傳回視窗位置,必須叫server在視窗位置變化時通知client,client自己記錄位置。



這次的程式如下
#include<X11/Xlib.h>
#include<unistd.h>
//使用pause()

int main(){
  Display* dsp = XOpenDisplay(NULL);
  Window window = XCreateSimpleWindow(dsp,
    DefaultRootWindow(dsp),
    100,100,200,200, //x,y,w,h
    0,0, //border
    0); //backgd

  XMapWindow(dsp,window);
  XFlush(dsp);
  pause();

  XCloseDisplay(dsp);
  return 0;
}

Linux的檔名大小寫有別,include的時候大小寫不能打錯。

首先用XOpenDisplay()產生一個型態為Display*的變數,代表一條跟server的連線,之後送指令給server,以及接收server傳來的事件都要用這個變數。

再用XCreateSimpleWindow()產生一個Window型態的變數。
至於Window是什麼,翻X.h可以看出它是跟所在平台的指標一樣大的整數,具體意義大概要看X Window的內部構造才知道。
參數如下
1:上面建立的dsp。
2:上層視窗,這裡填root window,也就是代表整個螢幕的視窗。
3~6:x坐標,y坐標,寛,高。
7~9:border_width, border, background,此例沒用到所以都填0。

附帶一提,在X Window要使用OpenGL,必須準備的東西就是Display*和Window,之後把這兩個丟給OpenGL的初始化函式。

光是這樣視窗還不能出現在畫面上,接下來要用XMapWindow()送放置視窗的命令給server。
命令不會自動送出去,要呼叫XFlush()送出。
下一行pause()是暫停讓我們看到結果,否則程式立刻往下執行到結束就看不到了。

用這個指令build
gcc window.c -o window -Os -s -lX11
用IDE的話連結到libX11這個函式庫。

用命令列打./window執行,或是在檔案管理器用滑鼠點檔案執行。


可看到以下現象

1.Linux的程式沒有命令列和視窗模式之分,執行時不會出現命令列視窗。
如果程式有用printf輸出東西,要從命令列執行才看得到,點滑鼠執行就看不到了。
(所以Cyber Sprite Linux版出問題的時候,我會叫玩家用命令列執行看看,這樣才看得到訊息)

2.視窗工作區域就是200×200了,不用計算邊框的寬度,client的管轄範圍只有視窗工作區域,邊框是由window manager管轄。

3.視窗移動、關閉的事件都是在server處理,client不用跑迴圈處理事件。
但因此我們寫的程式不知道視窗何時關閉,這個例子按╳按鈕關視窗也不會結束程式,命令列要按Ctrl+C結束程式,如果是點滑鼠執行則process會殘留在系統裡,要開系統監控工具找到process將它砍掉。



接下來要改良第三點,加入事件處理的功能,叫server做某些事的時候通知client,並且加上標題。

#include<X11/Xutil.h> //間接引用Xlib.h
#include<stdio.h>

int main(){
  Display* dsp = XOpenDisplay(NULL);
  Window window = XCreateSimpleWindow(dsp,
    DefaultRootWindow(dsp),
    100,100,200,200, //x,y,w,h
    0,0, //border
    0); //backgd

  //設定事件mask
  const int eventMask=KeyPressMask;
  XSelectInput(dsp, window, eventMask);

  Atom wmDelete=XInternAtom(dsp,"WM_DELETE_WINDOW",False);
  XSetWMProtocols(dsp, window, &wmDelete, 1);

  //設標題
  XStoreName(dsp, window, "title");

  XMapWindow(dsp,window);
  //接收事件
  XEvent evt;
  int isRunning=1;
  while(isRunning){
    XNextEvent(dsp,&evt);
    switch(evt.type){
    case KeyPress:
      printf("key code %u\n",evt.xkey.keycode);
      break;
    case ClientMessage:
      if(evt.xclient.data.l[0]==wmDelete){
        XDestroyWindow(dsp, window);
        XFlush(dsp);
        isRunning=0;
      }
      break;
    }
  }

  XCloseDisplay(dsp);
  return 0;
}

建出視窗後要告訢server我們想接收哪些事件,這裡註冊了兩種事件。
第一種是server通知client的事件,宣告一個整數event mask再用XSelectInput()傳給server。這裡只註冊一個鍵盤按下事件的mask,如果有多個mask就用位元or將它們同時開啟。
第二種是client之間溝通的事件,視窗關閉是由window manager管轄,而window manager是另一個client,要用client之間溝通的方法。
使用XInternAtom和XSetWMProtocols兩個函式,WM_DELETE_WINDOW代表按╳按鈕時發個訊息通知我們寫的程式,但不會立刻關閉視窗,要手動呼叫XDestroyWindow()關視窗,這可以用在關視窗前跳一個確認訊息。(WM=window manager)

設定標題之後進入事件處理迴圈,XNextEvent()會讓程式停住直到有事件進來,這個函式同時有XFlush()的作用所以不用呼叫XFlush()了。
evt儲存了事件資訊,根據evt.type判斷是哪一種事件做不同處理。
查XEvent的定義可看到如下
typedef union _XEvent {
  int type;
  XAnyEvent xany;
  XKeyEvent xkey;
  XButtonEvent xbutton;
  ……
  XClientMessageEvent xclient;
  XMappingEvent xmapping;
  XErrorEvent xerror;
} XEvent;
是個union,xkey、xbutton、xclient這些struct都佔用相同空間,先檢查type的值再看要把這一塊資料解釋為哪個struct。

KeyPress是鍵盤按鈕按下,從evt.xkey取得按鈕的編號並用printf印出。
ClientMessage是client之間的訊息,前面註冊了WMProtocol,所以按╳時會執行到這一段,用if確認事件內容是"WM_DELETE_WINDOW"。
特別用isRunning=0是因為C語言的語法,如果在這裡寫break只能跳出switch,不能跳出while迴圈,所以用一個變數做記號再用它判斷跳出迴圈。

這個程式關視窗就可以讓程式結束了。




X Window能做的事比Windows API少得多,它的「視窗」只有在螢幕上開一塊矩形區域,沒有內建按鈕、下拉式選單等元件,畫出元件還有按鍵盤滑鼠之後發生什麼事得自行處理,這部分留給其他擴充函式庫實作。

遊戲只要開一塊矩形區域,之後畫圖、處理事件都是自己來,使用Xlib比較好,用直接的方法做事可減少多餘的東西,想用各種視窗元件的話就得用GTK、QT之類的framework。

本篇做的事還比較簡單,之後要做改變視窗大小、全螢幕模式就很難搞了。X Window難搞的地方除了它是client、server非同步執行以外,還有說明文件很少,這篇是我找到唯一解釋比較清楚的。
https://tronche.com/gui/x/
Xlib官網的說明也跟這篇差不多
https://www.x.org/wiki/ProgrammingDocumentation/
只看文件還是不知道怎麼寫X Window程式,我是看SDL的程式碼,看它在Linux怎麼架視窗才寫出來。

另外查到有個用來取代X Window的顯示系統wayland,用了或許有好處,可是有著比X Window還大的問題:說明文件更少,不知道程式該從何寫起,就決定先不理它。
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=3761151
All rights reserved. 版權所有,保留一切權利

相關創作

同標籤作品搜尋:Linux程式設計|X window|Linux|C語言|程式

留言共 0 篇留言

我要留言提醒:您尚未登入,請先登入再留言

8喜歡★shark0r 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:這幾天在研究無接縫til... 後一篇:巴哈姆特字型測試...

追蹤私訊切換新版閱覽

作品資料夾

ilove487奇幻小說連載中
《克蘇魯的黎明》0667.掉到海裡要先救誰?看更多我要大聲說昨天13:38


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】