Trying out Qt

ปกติเวลาลองของใหม่ๆ จะลงในเฟสอยู่เรื่อยๆ แต่จะตั้ง friend only ไว้เพราะว่าเดี๋ยวนี้วงการนี้ดราม่าเยอะ ถ้าจะกด public หรือลง blog ต้องมานั่ง fact check พอสมควรก่อนถึงจะออกได้

ปีนี้เนื่องจาก Facebook ปรับ UI ใหม่ให้น่ารำคาญขึ้น ก็คิดว่าน่าจะถึงเวลาเลิกแล้วกลับมาเขียนในบล็อค ก็เลยคิดว่าจะลองดูใหม่คือเอา content แนวเดิม formatting แย่ๆ แบบเดิมแต่เขียนลงบล็อคดูบ้าง

สำหรับคนที่อาจจะไม่เคยได้อ่าน content พวกนั้น, ปกติจะ:

  • เป็น first impression มากๆ คือไม่ได้ลึกและบางทีที่เชียร์ผ่านไปอีกสองวันก็ไม่เชียร์แล้ว แต่ไม่ได้กลับมาอัพเดตนะ
  • rapid fire จัดๆ เน้น bullet point คิดอะไรออก brain dump ลงไป
  • เขียนแบบคิดว่าคนอ่านเข้าใจใน topic นั้นแล้ว
  • ไม่ได้ proofread

ช่วงนี้โคลนแอพ Alt1 มารันใน Linux อยู่ (Alt1 เป็นโปรแกรมช่วยเล่นเกม RuneScape โดยมันจะ stream หน้าจอให้ web page แล้ว web page ทำ OCR) ก็พบว่าถึงโค้ดมันจะค่อนข้างแย่ แต่ไอเดียรอบๆ มันฉลาดดีแล้วทำให้ port ง่ายมาก เพราะ OCR ทั้งหมดไปรันใน JavaScript หมด

Goal คิดว่าอยากได้ Linux + Mac ก็เลยเลือก Qt ก่อนหน้านี้เคยใช้ Gtk กับ wxWidget มาแล้ว คิดว่า Gtk ไม่น่าจะรันในแมคได้ดี ส่วน wxWidget คิดว่า API จะไม่พอใช้

ปัญหาแรกที่เจอคือคนเขียน cefpython ไม่ได้อัพเดตต่อ คือไม่มีตังให้ก็ไม่ทำ ซึ่งก็ถูกของเค้าล่ะ ก็เลยต้องเปลี่ยน engine ไปใช้ QtWebEngine ซึ่งมันไม่ใช่ CEF แต่มันเป็น Embedded Chromium เหมือนกัน

พอเอา QtWebEngine มารันได้ ถัดมาที่ต้องทำคือจำลอง Alt1 API ซึ่งบน Alt1 มันจะมี window.alt1 ที่มีเมธอดต่างๆ เช่น getRegion เพื่อที่จะ transfer capture มายัง JavaScript ปัญหาก็คือ CEF3 ไม่มีท่าที่สามารถ provide native object ให้ web ได้แล้ว รวมถึง QtWebEngine ด้วย เหตุผลเพราะว่า Blink มันรันเป็น multiprocess แล้วทำให้ไม่สามารถแชร์ของข้าม process ไปได้ ก็จะมีแต่ Async API ให้เท่านั้น

ยังสงสัยอยู่ว่า Alt1 จะทำยังไง หรือจะไม่อัพเกรด CEF?

ก็นึกอยู่คืนหนึ่งว่าทำยังไงดี QtWebEngine ก็ไม่มีแนะนำ จนกระทั่งไปอ่าน docs ของ CEF แล้วมันเขียนว่าให้ทำ synchronous XHR ซึ่งทีแรกก็ลังเลเพราะมันจะ deprecate แล้ว แต่ก็ไม่มีทางเลือกเลยต้องทำ

พอ Get รูปได้รอบแรกดีใจมาก แต่ปัญหาคือพอไปลองกับ app จริงๆ แล้วมัน poll เอารูปค่อนข้างถี่มากๆ แล้ว encoder algorithm มันเขียนด้วย Python ซึ่งช้ามาก (ฝั่ง JS expect รูปเป็นแบบ color channel BGRA ซึ่งต้องสลับเอง) เลยลองไปเขียน Cython ดู พบว่าเขียนง่ายมากๆ และออกเป็น C จริงๆ มีให้ดูด้วย แถมเร็วมากๆ

(ตอนหลังมาพบว่ามัน split() แล้ว merge() เพื่อเรียง channel ใหม่ได้ เร็วกว่า cython ที่เขียนอีก…)

พอรันได้แล้วก็เลยลองหาวิธีต่อกับเกมจริงๆ บ้าง ตอน capture เกมพบว่า Qt สามารถเอา WId ให้มันแล้วมันจะ capture หน้าต่างได้ ซึ่งบน Linux ต้องใช้ Xlib หา X Window ID ออกมาแล้วส่งให้ ที่ Qt ทำคือมันจะแคปจอทั้งหมด แล้ว crop ให้เหลือแต่ด้านในของ Window นั้น ซึ่งก็ง่ายดีแต่แปลว่าห้ามมีอะไรบังหน้าต่าง

ไปต่อ Xlib ตรงๆ นี่ปวดหัวมาก ทำ segfault ไปหลายรอบ ยังขี้เกียจบ่นอยู่เพราะเพิ่งมารู้ว่าควรจะต่อกับ XCB แทน เดี๋ยวจะไปเขียนใหม่


ทีนี้ที่รู้สึกเจ๋งมากคือลอง port ไปรันใน Mac ดูบ้าง ส่วนที่ต้องทำก็คือ

  • หาวิธี list game window บน Mac
  • บน Mac WId คือ Window pointer ของ Quartz ซึ่งไม่มี representation ใน Python แปลว่าใช้ท่า capture เดิมไม่ได้เลย

ก็ลองเขียนต่อกับ Quartz ดูแล้วพบว่า API ของ Mac ใช้งานง่ายกว่าเยอะ docs คุณภาพดีกว่ามาก (ของ Python นี่ไม่มี API docs ต้องนั่งแกะซอร์สเทียบกับ protocol definition) งมอยู่ประมาณ 4 ชั่วโมงก็ใช้ได้ทั้ง 2 feature แถม capture window ที่โดนบังได้ด้วยเพราะ Mac มี API ที่ระบุหน้าต่างแล้วมันเอามาให้ได้เลย

หลังจากใช้งานพื้นฐานได้ ก็ hook กับ Accessibility API อีกหลายชั่วโมงก็สามารถจับ game resize, game activity (ถ้าไม่กดเมาส์นานๆ เกมจะ auto logout) และ hotkey ได้ เป็นอันสมบูรณ์ก่อน Linux

แต่ว่า port Mac จริงๆ ก็ยังมีปัญหาอยู่บ้าง นั่นคือ

  1. Capture API ใน Mac คืนกรอบหน้าต่างมาด้วย ถึงจะบอกว่าไม่เอามันจะไม่ให้เงามาแต่ยังให้กรอบอยู่ดี ส่งผลให้ API ที่ใช้ตำแหน่งต่างๆ เลื่อนหมด
  2. Mac ใช้ HiDPI (Retina display) เป็นหลักและมันทำให้พิกัดต่างๆ ปวดหัวมากว่าอันนี้เป็น screen size หรือ actual size และตัวเกมเองไม่ได้ support Retina ด้วย

ทีรู้สึกว่า Qt มันเจ๋งมากๆ คือด้านบนที่เล่ามามีแค่ port ส่วนที่ต่อกับเกมซึ่ง Qt ไม่มี API ที่ไปเชื่อมต่อกับหน้าต่างแอพอื่นๆ สักเท่าไร แต่ในส่วน browser แล้วใช้โค้ดเดียวกันรันได้ทุก platform เหมือนกันเป๊ะ คือ Qt abstract platform ให้ได้ดีมากๆ บวกกับ Python ด้วย

เดี๋ยวถึงตอนจะทำ embed window, custom decoration ค่อยมาดูว่าจะรอดมั้ย

Go look at my code >> https://github.com/whs/runekit


สุดท้ายซ่าอยากลอง เลยลอง Port ไปรันใน Windows ดูบ้าง

  • WId บน Windows ใช้ HWND ของ Windows ได้เลย
  • ฟังค์ชั่น capture ของ Qt รันบน HWND แล้วได้ window capture !! สุดยอดมาก
  • API Windows เท่าที่ดูยังงงๆ ว่าอะไรเรียกว่าอะไร แต่ถ้าเจอแล้วก็เรียกใช้ได้ง่ายกว่า Xlib บน Linux อยู่ดี…
Real Alt1 vs clone

เขียน project นี้แล้วก็เริ่มรู้สึกเหมือนตอนเด็กกว่านี้ว่าต่อไปนี้เราทำ Qt UI ดีกว่า เขียนทีเดียวรันได้ทุกที่ แถม native ไม่ต้องใช้แล้ว React Native หรือ Flutter

แต่ปัญหาใหญ่ของ Qt คือมันเป็นบริษัทเขียนมันถึงจะออกมาได้ดีขนาดนี้ และบริษัทเองก็ให้ใช้งานภายใต้ LGPL ฟรี ปัญหาคือพอเป็น mobile app แล้ว โดยเฉพาะ iOS จะค่อนข้างยากที่จะทำให้ user สามารถ replace LGPL library เองได้ ถ้าจะซื้อรุ่นเสียเงินก็ราคาไม่ได้ถูกเลย โดยเฉพาะเมื่อเทียบกับ React Native หรือ Flutter ที่มันใช้ฟรี

แต่ทั้งสอง project เอง ก็ยังรู้สึกว่าเป็น corporate back อยู่ประมาณหนึ่งไม่ได้มี community มาทำแทนกันได้หมด ถึงจะมีคนใช้มากแต่ถ้า sponsor หลักไม่ทำก็อาจจะ innovate ไม่ทันเทคโนโลยีอื่นได้ ก็เป็นปัญหา classic ของ open source ที่ corporate ใช้ว่า take อย่างเดียวแต่ไม่ give