اختاپوس خسته

یادداشت‌هایی پیرامون کد، زندگی و دوستان

بدترین باگ کیوت!

کیوت به‌نظر من بهترین فریم‌ورک سی‌پلاس‌پلاس هست. یک کتابخانهٔ خیلی بزرگ با امکانات بسیار عالی و خوب که محیط کاری KDE به‌طور کامل برپایهٔ اون ساخته شده. اما از نسخهٔ 5.3 (ظاهراً) یک باگی به‌وجود آمده که زندگی رو برای ما خیلی سخت کرده: عدم امکان وارد کردن نویسه‌های کنترلی و نیم‌فاصله‌های چسبان و غیرچسبان! (قبلاً در مورد مورد نویسه‌های کنترلی و جهت‌دهی متون فارسی/انگلیسی نوشتم.) حوزهٔ تأثیر این باگ به قدری بزرگ و گسترده است که کار با محیط KDE رو برای ما غیرممکن کرده. تصور کنید که تقریباً هیچ جایی توی سیستم‌عامل و برنامه‌های کاربردی محیط دسکتاپ نتونید نویسه‌های کنترلی و فاصله‌ها رو تایپ کنید! توی این نوشته قصد دارم دلایل این باگ و روش برطرف کردنش و همچنین روش دور زدن اون رو توضیح بدم (: خوشبختانه روش فیکس خیلی ساده‌ست. و البته جای نگرانی نیست: نسخه‌های فیکس با کیوت 5.8.1 (اگر ریلیز بشه) و یا 5.9.0 (در هر صورت) منتشر میشن. منتها کسایی که نمی‌خوان تا اواسط 2018 صبر کنن که اون نسخه‌ها برای دبیان و اوبونتو بیاد، خودشون می‌تونن با این روشی که توضیح میدم پچ کنن (:

شروع ماجرا

همه‌چیز توی کیوت به خوبی و خوشی پیش می‌رفت. نسخهٔ 5 تازه منشتر شده بود و چند تا هم نسخهٔ minor خورده بود و همهٔ باگ‌های اساسی داشتن به مرور برطرف می‌شدن که QTBUG-35734 اتفاق افتاد. توی نسخهٔ 5.2 یک نفر گزارش کرد که کلیدهای کنترل و شیفت موقع تایپ نویسه‌های نشانه‌گذاری آلمانی (مثل دونقطهٔ بالایی Ü) درست کار نمی‌کنن. فیکس مربوطه خیلی ساده بود: حذف تمام نویسه‌های غیرقابل نمایش (چیزهایی که پرینت نمیشن) که با شیفت یا کنترل و شیفت وارد میشن از ورودی‌های کیوت (تمام ورودی‌ها سابسیستم ویجت‌ها). این فیکس توی نسخهٔ 5.4 منتشر شد و الان که 5.8 رو داریم هنوز پابرجاست. نتیجه این که محدودهٔ گسترده‌ای از نویسه‌های کنترلی جهت و نیم‌فاصله رو نمی‌تونیم با کلیدها وارد کنیم. (البته می‌تونیم کپی کنیم از جای دیگه، ولی خوب به درد کی می‌خوره؟)

برطرف شدن مشکل

مشکل مربوطه توسط پنج نفر (که یکیش خودم باشم) به‌طور مستقل گزارش شده. اولین گزارش باگ به تاریخ ۲۹ مهر ۱۳۹۳ اینجا بود: QTBUG-42074 و چندین مورد هم مثل QTBUG-55608 و QTBUG-57302 و QTBUG-57003 و QTBUG-58364 بعدش گزارش شدن. ولی فیکس مربوطه میرسه به تاریخ ۲۸ دی ۱۳۹۵ ! یعنی ما دو سال آزگار باید منتظر فیکس شدن این معضل می‌موندیم. منشأ این مشکل ضعف جامعهٔ کاربری توسعه‌دهنده‌های ایرانی هست. وقتی سطح مشارکت ما این‌قدر پایین باشه و صرفاً دنبال راه‌کارهای سودآور بدون صرف هیچ زمان و هزینه‌ای باشیم؛ اونوقت وضعیت‌مون میشه همین. تازه مشکل با فیکس شدن باگ هم حل نمیشه! ما معمولاً از توزیع‌های مخازن استفاده می‌کنیم که خیلی دیر به‌روز میشن. خیلی دیر یعنی این که نسخهٔ فعلی کیوت توی مخازن اوبونتو برابر 5.6.1 هست، و نسخهٔ تصمیم‌گیری شده برای انتشار بعدی (شش ماه بعد) 5.7.1 هست. یعنی حداقل باید برای رفع این مشکل یک سال دیگه (اون هم با اما و اگر) صبر کنیم. کاربران دبیان اما شرایط بدتری دارند. کاربران ردهت خیلی بدتر از حتی دبیان!

راه‌حل‌های غیررسمی

تلگرام یک پچ غیررسمی روی کدهای کیوت منتشر کرد که به لطف این پچ می‌تونیم از نویسه‌های نیم‌فاصله و نیم‌فاصلهٔ چسبان توی تلگرام استفاده کنیم. اما هنوز نمی‌تونیم از تغییردهنده‌های جهت استفاده کنیم…

از طرفی هیچ توزیع لینوکسی هیچ وصله‌ای رو ارائه نکرده برای این مشکل. بنابراین هرکسی با هر نسخه‌ای از کیوت که نصب داره، باید پچ خودش رو بنویسه؛ که از طرفی با پچ‌های خود توزیع روی کیوت هم تداخل پیدا نکنه. خوب این کار رو من کردم و نتیجه گرفتم. البته به‌صورت نصفه نیمه: فقط ویجت‌ها درست شدن. کیوت کوییک (مخصوصاً kate که ظاهراً به همراه KF5 مهاجرت کرده به رابط کاربری جدید کیوت) مشکلش پابرجاست. به‌زودی برای اون هم راه‌حلی پیدا می‌کنم.

راه حل من مبتنی بر اوبونتو هست که با کمترین تغییرات برای دبیان هم قابل اعمال هست. روال مشابهی برای توزیع‌های دیگه باید انجام بشه.

برای حل این مشکل ما باید کتابخانهٔ Qt Core رو پچ کنیم. پچ کردن یا وصله زدن به فرایند ایجاد تغییرات جزئی توی سورس اصلی یک بستهٔ نرم‌افزاری گفته میشه. ما این کار رو کاملاً در چهارچوب طراحی و استانداردهای مخازن اوبونتو (و دبیان) انجام خواهیم داد. برای این کار اول باید بسته‌های سورس کتابخانهٔ مورد نظر رو دانلود کنیم:

1
$ ‌‌apt source libqt5core5a

این دستور بستهٔ نرم‌افزار حامل سورس کتابخانهٔ Qt Core رو به همراه تمام وصله‌های مربوط به اوبونتو برای ما دانلود خواهد کرد. در مجموع چیزی حدود پنجاه مگابایت. اگر به خروجی دقت کنید می‌بینید که آدرس رپوزیتوی گیت هم داده شده که بعداً می‌تونیم از اون برای ارسال وصله برای ریلیز توی مخازن اوبونتو استفاده کنیم:

1
2
3
4
5
6
7
8
9
10
11
12
$ Reading package lists... Done
Picking 'qtbase-opensource-src' as source package instead of 'libqt5core5a'
NOTICE: 'qtbase-opensource-src' packaging is maintained in the 'Git' version
control system at:
https://anonscm.debian.org/git/pkg-kde/qt/qtbase.git
Please use:
git clone https://anonscm.debian.org/git/pkg-kde/qt/qtbase.git
to retrieve the latest (possibly unreleased) updates to the package.
Need to get 47.5 MB of source archives.
Get:1 http://gb.archive.ubuntu.com/ubuntu xenial-updates/main/qtbase-opensource-src 5.5.1+dfsg-16ubuntu7.2 (dsc) [5,096 B]
Get:2 http://gb.archive.ubuntu.com/ubuntu xenial-updates/main/qtbase-opensource-src 5.5.1+dfsg-16ubuntu7.2 (tar) [47.2 MB]
...

خوب حالا سورس اصلی رو (بدون اعمال وصله‌ها) توی مسیر مربوطه داریم. الان باید وصلهٔ خودمون رو اعمال کنیم. خوب من می‌دونم که مشکل مربوطه توی فایلی به اسم qwidgettextcontrol.cpp در مسیر src/widgets/widgets/ هست. خوب به اون مسیر میرم و قسمت‌هایی که میخوام رو تغییر میدم. روش انجام این تغییر مهم هست و باید به شکل اصولی انجام بشه تا قابل اشتراک گذاری با دیگران باشه:

1
2
3
4
5
6
7
8
9
10
$ cd qtbase-opensource-src-5.5.1+dfsg/
$ export QUILT_PATCHES=debian/patches
$ quilt push -a
File series fully applied, ends at patch debian/patches/fix-duplicate-qnam-finished.patch
$ quilt new fix-non-printable-filters-for-persian-keyboard
Patch debian/patches/fix-non-printable-filters-for-persian-keyboard is now on top
$ quilt add src/widgets/widgets/qwidgettextcontrol.cpp
File src/widgets/widgets/qwidgettextcontrol.cpp added to patch debian/patches/fix-non-printable-filters-for-persian-keyboard
$ quilt add src/widgets/widgets/qwidgetlinecontrol.cpp
File src/widgets/widgets/qwidgetlinecontrol.cpp added to patch debian/patches/fix-non-printable-filters-for-persian-keyboard

با این اجرای دستورات؛ فایل‌ها رو برای تعقیب تغییرات نشان‌گذاری می‌کنیم. تعقیب تغییرات یعنی این که به سیستم میگیم که: حواست باشه کدوم قسمت از فایل رو به چه شکلی تغییر میدم و در نهایت وقتی ازت پرسیدم چه کاری انجام شد، در فرمت ازپیش‌تعریف‌‌شدهٔ patch، به من بگو که دقیقاً چه کارهایی انجام شد.

خوب، بعد از اصلاح فایل‌ها (اضافه کردن چند خط کد مربوط به شرط عدم پذیرش نویسه‌ها که بعداً در موردش توضیح میدم) می‌رسیم به مرحله‌ای که می‌خواهیم وصلهٔ مربوطه رو تشکیل بدیم و کدها رو کامپایل کنیم. برای درست کردن وصله باید دستورات زیر رو وارد کنیم. با اجرای دستور اول وصله ساخته میشه و با اجرای دستور دوم تغییراتی که برای ساخت وصله انجام داده بودیم، به حالت اول برگردانده میشه.

1
2
$ quilt refresh
$ quilt pop -a

خوب حالا طبق استاندارد باید یک واردهٔ جدید به فایل ChangeLog اضافه کنیم که توضیحات مختصری در مورد وصله‌ای که ارائه دادیم رو داشته باشه. برای این کار می‌تونیم به شکل دستی فایل مورد نظر رو ویرایش کنیم و یا از دستور dch -i استفاده کنیم که کار رو برای ما راحت‌تر می‌کنه. این دستور از ما می‌پرسه ادیتور موردعلاقه‌مون چیه و بعد با استفاده از اون ادیتور یک واردهٔ استاندارد به ابتدای فایل ChangeLog اضافه می‌کنه. محتویات فایل ChangeLog در نهایت به‌صورت زیر خواهد بود. (دقت کنید که نسخهٔ پکیج‌های ساخته شده دقیقاً یکی بیشتر از نسخهٔ پکیج اصلی)

1
2
3
4
5
6
qtbase-opensource-src (5.5.1+dfsg-16ubuntu7.3) UNRELEASED; urgency=medium

  * Add a temporary workaround for QTBUG-42074

 -- Soroush Rabiei <soroush@ametisco.ir>  Tue, 09 May 2017 20:23:08 +0430
...

بعد از این کار آمادهٔ کامپایل و نصب بسته هستیم. دستورات زیر این کار رو برای ما انجام میدن:

1
$ DEB_BUILD_OPTIONS=nocheck debuild -us -uc -b -j10

من اینجا تست‌های اتوماتیک رو با متغیر محیطی DEB_BUILD_OPTIONS غیرفعال کردم چون نمی‌خوام بعد از کامپایل پکیج‌ها کلی وقت صرف تست اون‌ها بکنم. روال کامپایل رو هم ده‌هسته‌ای تعریف کردم (قانون دو+تعداد هسته‌های پردازنده). بعد از حدود ده دقیقه روی ماشین من (i7-6700) پکیج‌ها آمادهٔ نصب هستند و در دایرکتوری بالاتر به‌صورت مجموعه‌ای از فایل‌های .deb قابل دسترس هستند. البته این فایل‌ها توی دایرکتوری بالاتر ایجاد میشن نه کنار سورس‌ها. من این‌ها رو میذارم توی یک رپوزیتوری شخصی که از پکیج‌هام درست کردم که بعداً هم بتونم نصبشون کنم. البته می‌تونم مستقیماً با این دستور پکیج‌های ساخته شده رو نصب کنم:

1
2
$ cd .. # برای رفتن به دایرکتوری بالاتر
$ sudo debi # برای نصب پکیج‌های تولید شده

نتایج

خوب حالا با خیال راحت توی برنامه‌های ویجتی کیوت می‌تونم از نیم‌فاصله و کاراکترهای کنترلی استفاده کنم:

پی‌نوشت: وصله‌ها

خوب وصله‌ای که من برای ویجت‌ها به‌کار بردم این‌طور عمل می‌کنه: یک لیست سفید از نویسه‌هایی که نباید توی شرط مربوط به فیکس 35724 فیلتر بشن رو اضافه می‌کنم و هر سری که چیزی توی متن نوشته شد بررسی میشه که اگر شرط 35724 صدق کنه یا کاراکتر توی لیست سفید باشه؛ در این‌صورت اون رو می‌نویسه. وصلهٔ مربوطه این هست:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Index: 
qtbase-opensource-src-5.5.1+dfsg/src/widgets/widgets/qwidgetlinecontrol.cpp
===================================================================
--- 
qtbase-opensource-src-5.5.1+dfsg.orig/src/widgets/widgets/qwidgetlinecontrol.cpp
+++ qtbase-opensource-src-5.5.1+dfsg/src/widgets/widgets/qwidgetlinecontrol.cpp 


@@ -1884,7 +1884,11 @@ void QWidgetLineControl::processKeyEvent                 


         && event->modifiers() != Qt::ControlModifier


         && event->modifiers() != (Qt::ControlModifier | Qt::ShiftModifier)) {
         QString t = event->text();
-        if (!t.isEmpty() && t.at(0).isPrint()) {
+        ushort code = 0;
+        if(!t.isEmpty())
+            code = t.at(0).unicode();
+        if (!t.isEmpty() &&
+            (t.at(0).isPrint() || (0x2000 <= code && code <= 0x200F) || (0x2028 <= code && code <= 0x202F))) {
             insert(t);
 #ifndef QT_NO_COMPLETER
             complete(event->key());
Index: 
qtbase-opensource-src-5.5.1+dfsg/src/widgets/widgets/qwidgettextcontrol.cpp
===================================================================
--- 
qtbase-opensource-src-5.5.1+dfsg.orig/src/widgets/widgets/qwidgettextcontrol.cpp
+++ qtbase-opensource-src-5.5.1+dfsg/src/widgets/widgets/qwidgettextcontrol.cpp
@@ -1342,13 +1342,19 @@ void QWidgetTextControlPrivate::keyPress
 process:
     {
         // QTBUG-35734: ignore Ctrl/Ctrl+Shift; accept only AltGr (Alt+Ctrl) on German keyboards
-        if (e->modifiers() == Qt::ControlModifier
+        /* if (e->modifiers() == Qt::ControlModifier
             || e->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) {
             e->ignore();
             return;
         }
+        */
         QString text = e->text();
-        if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == 
QLatin1Char('\t'))) {
+        ushort code = 0;
+        if(!text.isEmpty())
+            code = text.at(0).unicode();
+        if (!text.isEmpty()
+                && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t')
+                    || (0x2000 <= code && code <= 0x200F) || (0x2028 <= code && code <= 0x202F))) {
             if (overwriteMode // no need to call deleteChar() if we have a selection, insertText
                 // does it already

پی‌نوشت ۲: کارهای باقی‌مانده

با اِعمال این وصله، باگ مربوطه در زمینهٔ ویجت‌ها برطرف شده؛ اما باگ مشابهی هنوز توی Qt Quick وجود داره. این باگ باعث میشه اون دسته از برنامه‌های KDE که به سیستم جدید KF5 مهاجرت کردن هنوز این مشکل رو داشته باشن. هنوز نمی‌دونم فیکسش چطور هست. بهرحال اگر پیداش کنم، به‌همراه وصلهٔ ویجت‌ها منتشرش می‌کنم. اگر به نسخهٔ رپوزیتوری نرسه (که با توجه به سنگین بودن کیوت امکان پذیرشش کم هست) این فیکس‌ها رو داخل یک PPA منتشر می‌کنم.

دیدگاه‌ها