Swift包管理器:在Linux上創(chuàng)建和使用X11包

作者:Benedikt Terhechte,原文鏈接,原文日期:2015-12-08
譯者:小鍋;校對:Prayer;定稿:

現(xiàn)在 Swift 已經(jīng)開源了,應(yīng)該有很多感興趣的用戶已經(jīng)在他們的 Linux 系統(tǒng)上安裝把玩過了。然而,目前的 Foundation 框架還在緊鑼密鼓地開發(fā)中,所以如果你想開發(fā)出比命令行更復(fù)雜一點的程序,就得鏈接已有的 Linux 庫,如 GTK, Cairo 或者 libpng 以開發(fā)出圖形界面的程序。

我剛剛實現(xiàn)了一個簡單的 Swift 程序,這個程序鏈接了 Unix 中最基本的 UI 類庫1 —— X11。在這個簡短的教程中,我會向你展示如果完成一個簡單的 X11 應(yīng)用程序,然后你就可以利用這些知識去鏈接到其它的類庫。

在開發(fā)的過程中,我們也會使用到新的 Swift 包管理器來為 X11 類庫創(chuàng)建一個簡單并且可重用的包。

下面是程序運行后的截圖2

Swift 包管理器

在實際開始開發(fā)我們的 X11 應(yīng)用之前,需要先定義一個 Swift 包用來定義鏈接到 X11 類庫。一旦完成這個步驟,之后我們就可以將這個包分發(fā)給其它開發(fā)者,或者在別的項目中進行重用。我們必須將定義一個包和使用一個包區(qū)分開來。讓我們先從定義開始,然后再學(xué)習(xí)如何使用。

定義一個包

新建一個目錄用來保存我們的包。由于我們需要鏈接到 C 語言的庫,我遵循了 Swift 包管理器文檔的指南,為包名加上一個 C 的前綴,將包命名為 CX11.swift

bash
mkdir CX11.swift

對我們來說,我們不想寫任何的 Swift 包裝(wrapper)代碼,而是要直接對已有的 C 語言 API 進行鏈接。要鏈接到 C 語言類庫和頭文件需要通過 module.modulemap 文件,這個文件里包含了幫助 Swift 編譯器來進行正確的鏈接操作的必要指令信息。可以在這里獲取 modulemap 的語法文檔。創(chuàng)建一個 module map 文件,并且對其進行編輯(你可以自行選擇編輯器,在這里我使用了 nano):

bash
touch CX11.swift/module.modulemap
nano CX11.swift/module.modulemap

X11 是一個包含了很多功能的大型類庫。你可以在 /usr/include/X11 目錄下查看到它的內(nèi)容。在我們的示例中,我們不需要包含所有的頭文件,相對地,我們只需要用到其中的兩個: Xlib.hX.h。對于其它的類庫,你可能需要包含更多的頭文件。在下面的內(nèi)容在我會提到如何用一種簡便方法來包含某個目錄下的全部頭文件。除了包含的頭文件,我們還需要告訴 Swift 鏈接哪一個庫??梢允褂?link 關(guān)鍵字來完成。我們的 modulemap 文件看起來應(yīng)該是這樣的:

module CX11 [system] {
  module Xlib {
      header "/usr/include/X11/Xlib.h"
  }
  module X {
      header "/usr/include/X11/X.h"
  }
  link "X11"
}

我們將所創(chuàng)建的模塊(module)命名為 CX11,并且我們創(chuàng)建了兩個子模塊(submodules)。一個是 Xlib,另一個是 X。每一個子模塊定義了它需要導(dǎo)入的頭文件。最后我們使用 link 語句來鏈接到 libx11 類庫。

但是如果我們想鏈接到不止一個頭文件呢?Module maps 允許我們定義一個 umbrella 頭文件或指定一個 umbrella 目錄。

Umbrella Header 這是一個頭文件,里面包含了引用(通過 #include)其它頭文件的指令。一個好的示例是 <Cocoa/Cocoa.h><Foundation/Foundation.h> 還有 <gtk/gtk.h>。使用 Umbrella 關(guān)鍵字來定義 Umbrella 頭:

umbrella header "/usr/include/gtk/gtk.h"

Umbrella Directory 有時候你有一個頭文件目錄但是并沒有一個 umbrella header。在這種情況下,你可以告訴 Swift 直接到該目錄下查找頭文件:

umbrella "/usr/include/X11/"

除了 modulemap 文件,我們還需要一個 Package.swift 文件,否則我們的構(gòu)建將會失敗。但是這個文件可以是空的:

bash
touch CX11.swift/Package.swift

Swift 包管理器使用了 Git 和 Git Tags 來對包進行管理。所以我們還需要為我們的包創(chuàng)建一個 Git 倉庫,添加所有的文件,然后打上一個版本標簽。這是相當容易的:

bash
cd CX11.swift
git init
git add .
git commit -m "Initial Import"
git tag 1.0.0
cd ..

上述命令首先切換到目錄中,創(chuàng)建一個 Git 倉庫,添加所有的文件到倉庫中,提交,最后為這個提交添加一個版本標記(1.0.0)。

就是這些,我們的包已經(jīng)定義完成了,那我們應(yīng)該如何來使用呢?

包的使用

要使用一個包,我們要先定義一個 Package.swift 文件,這個文件可以告訴 Swift 需要導(dǎo)入哪些包到我們的項目中。但是首先得為我們的項目創(chuàng)建一個目錄。

bash
mkdir swift-x11
touch swift-x11/Package.swift
touch swift-x11/main.swift

需要注意的是(針對這個特定的示例程序)需要將 swift-x 目錄與 CX11.swift 放到同一個目錄:

bash
ls -l
CX11.swift
swift-x11

在真正著手開始寫 Swift 代碼與 X11 進行交互之前,我們需要告訴 swift-x11 項目如何導(dǎo)入 CX11 包。在 swift-x11/Package.swift中輸入這些代碼,我會在下面具體解釋這些代碼的意思:

import PackageDescription

let package = Package(
  dependencies: [
    .Package(url: "../CX11.swift", majorVersion: 1)
  ]
)

這些代碼的意思是告訴 Swift 要到 ../CX11.swift 目錄中查找包。

url(正如它的名字所代表的)不需要是一個本地的url,我已經(jīng)將我的 CX11.swift 上傳到了 GitHub,你也可以在 url 中直接使用 GitHub 的鏈接:

bash
import PackageDescription

let package = Package(
  dependencies: [
    .Package(url: "https://github.com/terhechte/CX11.swift.git", majorVersion: 1)
  ]
)

使用X11

現(xiàn)在我們已經(jīng)定義好了 X11 包,并且包管理器也配置完畢,現(xiàn)在就開始動手用 Swift 寫第一個 X11 程序吧。

這里有一個問題我沒辦法解決,那就是定義在 X11 頭文件中的宏并沒有導(dǎo)入到 Swift 當中。Xlib.h 文件當中定義了很多類似下面這樣的宏:

c
#define RootWindow(dpy, src) (ScreenOfDisplay(dpy, src)->root)
#define ScreenOfDisplay(dpy, scr)(&((_XPrivDisplay)dpy)->screens[scr])

因為這些宏都沒有被導(dǎo)入,所以我決定將宏所定義的完整代碼都寫出來。以下所有的代碼都是寫在 main.swift 文件當中的。你也可以在 GitHub 上找到最終完成版。請注意,這是一個簡單并且內(nèi)存不安全的示例程序。這僅僅是為了展示如果在 Linux 下使用 C 語言類庫。同時,我的 X11 知識也十分有限。我在 Linux 下使用 X11 編程已經(jīng)是 10 年前的事情了,我已經(jīng)忘了大半,因此下面的代碼解釋中可能存在錯誤。如果你發(fā)現(xiàn)了錯誤,盡管在這個倉庫中向我提交一個PR :)

我們先從導(dǎo)入前面定義的 CX11 類庫開始:

import CX11.Xlib
import CX11.X

配置

在這之后,我們需要定義一些變量。

  • 我們需要一個 X11 顯示(大致來講就是與 X11 服務(wù)器的連接),這個變量命名為 d。
  • 我們需要為創(chuàng)建的 X11 窗口(window)提示一個占位符,這個變量命名為 w。
  • 我們還需要為 X11 服務(wù)器開辟一些內(nèi)存用于存儲 X11 輸入事件,這個變量命名為 e
  • 我們還需要保存要在窗口上顯示的文本,這個變量命名為 msg。
  • 我們需要一個位置用來儲存當前的 X11 屏幕(一個 X11 顯示可以擁有多個屏幕),這個變量命名為 s
  • 最后,我們需要一個指針指向 X11 的根窗口,這個根窗口保存了其它的窗口,這個變量命名為 rootWindow
// X11 Display
var d: _XPrivDisplay

// 我們將要創(chuàng)建的 window
var w: Window

// X11 產(chǎn)生的事件
var e = UnsafeMutablePointer<_XEvent>.alloc(1)

// 要顯示的文本內(nèi)容
var msg = "Hello Swift World"

// 指向當前 X11 Screen 的指針
var s: UnsafeMutablePointer<Screen>

變量定義好之后,我們需要打開到 X11 服務(wù)器的連接。但是,由于用戶有可能在沒有安裝 X11 服務(wù)器(比如,控制臺模式)的機器上運行這個應(yīng)用程序,所以我們需要判斷這個連接是否成功:

d = XOpenDisplay(nil)
if d == nil {
    fatalError("cannot open display")
}

在成功打開連接之后,我們要獲取當前的默認顯示屏以及當前的根窗口。由于 RootWindow 宏不可用4,所以我們直接獲取 C 結(jié)構(gòu)體 的內(nèi)存區(qū)域。然而,因為當前的屏幕 s 是一個 UnsafeMutablePointer,我們需要增加一個 memory 屬性來獲得 root 實例。

// Get the default screen
s = XDefaultScreenOfDisplay(d)

// And the current root window on that screen
let rootWindow = s.memory.root

創(chuàng)建一個窗口

現(xiàn)在我們有了創(chuàng)建窗口以及將其顯示在屏幕中的所有東西。我們將使用 XCreateSimpleWindow 函數(shù)來完成。這個函數(shù)接受如下的參數(shù):

c
XCreateSimpleWindow(Display *display, Window parent, int x, int y, 
  unsigned int width, unsigned int height, unsigned int border_width, 
  unsigned long border, unsigned long background);

borderbackground 是顏色(color)值。為了避免手動創(chuàng)建顏色,我們簡單地傳入當前屏幕定義好的默認黑色和白色引用。這里需要再次用到 .memory 屬性。

// Create our window
w = XCreateSimpleWindow(d, rootWindow, 10, 10, 200, 100, 1, 
  s.memory.black_pixel, s.memory.white_pixel)

這段代碼會在 rootWindow 的 10/10 位位置創(chuàng)建一個寬度 200 和高度 100 的新窗口。邊框會是黑色,而背景將會是白色。

輸入事件

當然,我們還需要接受從 Xserver 上傳來的輸入事件。在這個例子中,我們需要知道窗口何時被顯示,此時我們可以在上面進行繪制,我們還需要知道用戶按下特定鍵退出程序的事件。第一個是 Expose 事件,第二個是 KeyPress 事件。接受事件需要通過 XselectInput 函數(shù)來注冊事件掩碼來完成

c
XSelectInput(d, w, ExposureMask | KeyPressMask)

窗口創(chuàng)建完成之后,我們就可以顯示它了。這是通過 XMapWindow 函數(shù)來實現(xiàn)的

c
XMapWindow(d, w)

事件循環(huán)(Event Loop)

最后,我們需要在窗口的顯示期間啟動事件循環(huán)。在這里,我用一個 while 循環(huán)來不斷地使用 XNextEvent 函數(shù)以獲取新的 X11 事件。接著,我們對事件進行判斷,以確定其是否為 Expose 以及 KeyPress 事件5。我們通過 swift 語句來進行判斷:

loop: while true {

  // Wait for the next event
  XNextEvent(d, e)

  switch e.memory.type {
    // The window has to be drawn
    case Expose:
    // draw a small black rectangle
    XFillRectangle(d, w, s.memory.default_gc, 20, 20, 10, 10) 
    // draw the text
    XDrawString(d, w, s.memory.default_gc, 10, 70, msg, Int32(msg.characters.count)) 

    // The user did press a key
    case KeyPress:
    break loop

    // We never signed up for this event
    default: fatalError("Unknown Event")

  }
}

這里的 e 事件結(jié)構(gòu)體還是一個 UnsafeMutablePointer,所以我們還是需要通過 memory 屬性來獲得真實的結(jié)構(gòu)體。Expose 事件表明現(xiàn)在窗口已經(jīng)可見,所以我們需要對進行重繪。這里的繪制十分簡單:使用 XFillRectangle 來繪制一個小的黑塊,以及 XDrawString 來將已初始化的 msg 文本繪制到窗口的 10, 70 位置。我不是很清楚 X11 是接受 unicode 還是 ascii 編碼,所以 Int32(msg.characters.count) 可能是錯的,但是在這個例子當中它可以正常運行。

另一個事件,KeyPress 一旦在用戶按下一個鍵的時候跳出外層的 while 循環(huán)并退出程序。

運行

要運行這個程序,只需要 check out 倉庫(最好在Linux上進行)并且在目錄中運行如下命令:

bash
swift build

這個命令會 clone CX11.swift 包,并且在 .build/debug 目錄中構(gòu)建出二進制文件。

通過如下的命令來運行:

bash
.build/debug/swift-x11-example

這將會執(zhí)行二進制文件,一個小小的 X11 窗口將會出現(xiàn)在你的桌面上:

總結(jié)

這是一個相當?shù)暮唵蔚氖纠?,展示了如何?Linux 下使用 Swift 寫一個 X11 應(yīng)用程序。當然,這些知識同樣適用地鏈接到其它類庫的不同類型的應(yīng)用程序。這個教程同時也通過使用一個簡單的 X11 包闡述了 Swift 包管理器是如何工作的。

完整的 X11 應(yīng)用程序代碼可以在這里找到。
完成的 X11 包代碼可以在這里找到。

  1. 我一開始使用的是 GTK3, 但是沒辦法讓它運行起來
  2. 十分壯觀,不是么 :)
  3. "我們希望社區(qū)采用的約定是為這種模塊加上 C 的前綴然后與Swift模塊一樣使用駝峰命名。這樣就可以為其它更加'Swifty'的純C接口的包裝函數(shù)使用如JPEG這樣的命名"
  4. 參見上面的解釋,我沒辦法找出具體的原因
  5. 我們僅注冊過這兩個事件

本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán),最新文章請訪問 http://swift.gg。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,304評論 4 61
  • 轉(zhuǎn)載自:https://github.com/Tim9Liu9/TimLiu-iOS 目錄 UI下拉刷新模糊效果A...
    袁俊亮技術(shù)博客閱讀 12,146評論 9 105
  • 說變就變,昨天還是彩霞滿天,遍地金黃,今天拉開窗簾卻是滿眼銀裝。沒有風,是昨晚悄悄地來的,變得那樣突然。點...
    小老鄭閱讀 367評論 0 0
  • 《求佛》 我合手 把你捧在 我的面前 你如一片 燦爛的花開 芬芳了我青春荒蕪的季節(jié) 我合手 把你捧在 我懷里 你如...
    墨子悅閱讀 367評論 0 0
  • 北宮連韻是一位將軍, 說是沙場的英雄人物, 長得卻一表人才, 沒有武者那樣的彪悍粗狂倒是有幾分文者的溫文爾雅。 都...
    車塵樸風閱讀 639評論 1 12

友情鏈接更多精彩內(nèi)容