刹那(せつな)の瞬き

Willkömmen! Ich heiße Setsuna. Haben Sie etwas Zeit für mich?

ひと足先にPS4版torneがバッファロー社製nasneに対応してた

久しぶりに PS4 を起動したら、torne がアップデートされました。

アップデート履歴を確認したら、株式会社バッファロー社製 nasne に対応との事。

f:id:infinity_volts:20210313161330p:plain

2021年春発売予定の製品よりもひと足早く、ソフトウェアが対応してくれました。
予定は未定でなくて良かったです。

所持してる nasne は現在も元気に稼働中なのですが、こんな記事を書くくらいには心配してました。

後はバッファロー社から製品の発売を待つばかりですね。 

KDE Plasma 5.21 が降りてきた

昨晩の事です。

KDE neon を立ち上げてたら「更新」通知がポップアップしました。
アップデートを進めたところ、やたらと削除&インストール対象が多いので何事かと。

大掛かりなデスクトップ環境の更新でもあるのかなー、と思いつつも更新。
大掛かりなので、念の為、OS再起動。
そして、ログイン画面に進んだところで、背景の壁紙が変更されてるのに気づきます。

@KdeNeonのツイートを確認したところ、Plasma 5.21 のリリースでした。

ウィジェットの更新が多かったなー、とは思ってたのですが 5.21 でしたか。
5.20 環境が快適すぎて、ロードマップの確認を怠ってました。

....

早速、テーマを Breeze Twilight に変更しました。

ダークテーマを使いたいけど Breeze Dark で文字が読めないアプリがあり、渋々 Breeze を使ってた私には Breeze Twilight がバランス良く感じられます。

すべてのアプリが Breeze Dark でも違和感なく表示されてれば不満はなかったのですが、背景色はテーマに合わせるけど文字色は黒のまま、なアプリは困ります。

壁紙も Milky Way に変えたので、ウィンドウの視認具合も良い感じです。

仕事用 PC が更に快適になりました。

....

アプリケーションランチャーが変わったとの事なので、少しだけ確認してみました。
以前のものよりは使い勝手が改善されてるので、合う人は多いと思います。

ただ、私は少しだけ合わないです。
2 クリックで操作したい、かつ、メニューに余計な情報は要らない、からです。
なので、代替ウィジェットの「アプリケーションメニュー」に切り替えて、「サブメニューを単一階層にフラット化」を有効にしてます。

軽くて、設定変更が豊富な KDE Plasma 5 は快適です。

KDE Plasma 5で日本語キーボードからドイツ語の文字やユーロ通貨記号を入力する

KDE Plasma 5 で日本語キーボードからドイツ語のウムラウトエスツェットを入力したい場合、Compose Key を設定するとキーストロークの組み合わせで特殊文字を入力できます。

後述する内容は KDE neon 5.20 と Kubuntu 20.10 で確認しました。

1. Compose Key を設定する

Comopse Key は初期状態では無効になっているので、まずは有効にします。

KDE システム設定を起動 ->「入力デバイス」->「キーボード」->「詳細」タブを選択し、「キーボードオプションを設定」をチェックします。

f:id:infinity_volts:20210120155454p:plain

ツリー表示の「Position of Compose Key」を展開 ->「Right Alt」にチェックをつけて ->「適用」ボタンをクリックすると、右AltキーがComposeキーになります。

設定はすぐに反映されるので、再ログイン等は不要です。

2. 特殊文字の入力

特殊文字は2段階入力になります。

例えば ä を入力するなら、最初にComposeShift"(フルキーの2)を同時に押して、一旦離してからAキーを押すと ä になります。

入力するキーの組み合わせ
文字 キーストローク1 キーストローク2
ä Compose+Shift+" A
Ä Compose+Shift+" Shift+A
ö Compose+Shift+" O
Ö Compose+Shift+" Shift+O
ü Compose+Shift+" U
Ü Compose+Shift+" Shift+U
ß Compose+S Compose+S
Compose+Shift+S Compose+Shift+S
Compose+Shift+= EまたはC

エスツェットの入力は少し違ってて、ComposeキーやShiftキーは押下したままでSを 2 度打鍵します。大文字にも対応してて嬉しいです。

3. おまけ

詳細はこちらのサイトが参考になります。

ついでに Unicode のコードポイントも貼っておきます。

コードポイント
文字 Unicode 文字 Unicode 文字 Unicode
ä U+00E4 ö U+00F6 ü U+00FC
Ä U+00C4 Ö U+00D6 Ü U+00DC
ß U+00DF U+1E9E U+20AC

 

なぜ必要だったのか

年に数回程度ですが、ネット検索でドイツ語の単語を入力する事があります。

ウムラウトエスツェットを含む単語は、特殊文字を使わなくても組み合わせで代用できるのは承知してるのですが、若干検索結果が変化します。

例えば Google 検索でオオカミを検索する場合、wölfe で約 24,400,000 件、woelfe で約 7,180,000 件と、結果と件数が随分違ってます。
なので、検索するキーワードは単語をそのまま入力したいのです。

ぼやき、というか困ってる事

Mac なら特に設定変更しなくてもoptionキーとの組み合わせで Umlaut や Eszett を扱えるので、今でもドイツ語を扱うなら基本的に Mac を使ってます。

しかし、Mac を立ち上げてない場合は別の PC で検索するのですが、これが Ubuntu だと入力できなくて困ってます。

Ubuntu 18.04 以降、GNOME Tweaks で Compose Key の設定をしたつもりでも有効にならず、2段階入力が失敗します。
Compose Key の設定値はgsettings get org.gnome.desktop.input-sources xkb-optionsで確認すると反映されているのですが、期待通りに動作しません。
/etc/dconf/ あたりの情報を漁っても解決できてません。

幸い Unicode のコードポイントを入力する方法なら可能ですが、ちょっと不便です。
いちいちコードを調べるならコード表から文字をコピペした方が楽ですし。

Ubuntu 20.10 になった現在でも正しい設定方法は不明です。
Ubuntu というか GNOME 利用者はどうしてるんだろう。

一方で、KDE neon / Kubuntu なら設定は必要だけど、割とすんなり扱えたので、とりあえずは満足しました。やっぱり KDE Plasma 5 は快適ですわ。

Nanaのthreads::poolを利用してnanodbcの実行結果を表示する

Linux 環境でも ODBC 接続をスレッド内で利用可能なのは確認できました。

もう一歩進めるとスレッドプールやワーカースレッドの話が出てきますが、どうせならGUI を絡めてみたくなりました。

ウィンドウを表示して、そこに結果セットを表示したくなるのは自然だと思います。
今回は GUI framework に Nana C++ Library を利用してみました。

実行結果とソースコード

2 つのクエリを実行し、結果を表示するサンプルです。

開始ボタンをクリックすると、フォームに表示される2つの nana::listbox にそれぞれのクエリの実行結果を表示します。

f:id:infinity_volts:20201206155350g:plain

・main.cpp

nana::listbox に表示させたい処理をラムダ式で記述しておいて、それをスレッドプール(nana::threads::pool)に登録しつつ、開始ボタンのクリックイベントに割り当ててます。

#include <nana/gui.hpp>
#include <nana/gui/widgets/label.hpp>
#include <nana/gui/widgets/listbox.hpp>
#include <nana/gui/widgets/button.hpp>
#include <nana/threads/pool.hpp>
#include <nana/system/platform.hpp>
#include <nanodbc/nanodbc.h>

int main()
{
    std::locale::global(std::locale(""));
    std::string conn_str = "Driver={ODBC Driver 17 for SQL Server};Server=localhost;UID=sa;PWD=abcd1234$;Database=my_test_db;";

    nana::threads::pool th_pool;

    nana::form fm;
    fm.caption("Nana - nanodbc");
    fm.size(nana::size{ 450, 400 });
    
    nana::label lbl1{ fm, "SQLServer接続テスト  <bold blue size=12>MS版ODBCドライバ</>" };
    lbl1.format(true);

    nana::listbox list1{ fm };
    list1.append_header("コード");
    list1.append_header("氏名");
    list1.append_header("入社日");
    list1.show_header(true);
    list1.auto_draw(true);

    auto th1 = [ &conn_str, &list1 ] {
        list1.clear();
        nanodbc::connection conn(conn_str);
        nanodbc::string sql_text(NANODBC_TEXT("SELECT コード,氏名,入社日 FROM 社員 WHERE コード<300 ORDER BY コード"));
        nanodbc::result rs = nanodbc::execute(conn, sql_text);
        for (const auto& row : rs) {
            auto cat = list1.at(0);
            auto val_int = row.get<nanodbc::string>("コード");
            auto val_str = row.get<nanodbc::string>("氏名");
            auto val_date = row.get<nanodbc::string>("入社日");
            cat.append(std::initializer_list<nanodbc::string>{ val_int, val_str, val_date });
            nana::system::sleep(100);  // 0.1 sec
        }
    };

    nana::listbox list2{ fm };
    list2.append_header("コード");
    list2.append_header("氏名");
    list2.append_header("入社日");
    list2.show_header(true);
    list2.auto_draw(true);

    auto th2 = [ &conn_str, &list2 ] {
        list2.clear();
        nanodbc::connection conn(conn_str);
        nanodbc::string sql_text(NANODBC_TEXT("SELECT コード,氏名,入社日 FROM 社員 WHERE コード>199 ORDER BY 入社日"));
        nanodbc::result rs = nanodbc::execute(conn, sql_text);
        for (const auto& row : rs) {
            auto cat = list2.at(0);
            auto val_int = row.get<nanodbc::string>("コード");
            auto val_str = row.get<nanodbc::string>("氏名");
            auto val_date = row.get<nanodbc::string>("入社日");
            cat.push_back(val_int);
            cat.back().text(1, val_str);
            cat.back().text(2, val_date);	
            nana::system::sleep(100);  // 0.1 sec
        }
    };

    nana::button btn_start{ fm, "開始" };
    btn_start.events().click(nana::threads::pool_push(th_pool, th1));
    btn_start.events().click(nana::threads::pool_push(th_pool, th2));

    nana::button btn_close{ fm, "閉じる" };
    btn_close.events().click([ &fm ] {
        fm.close();
    });

    nana::place fm_place{ fm };
    fm_place.div("<><weight=90% vertical<><weight=95% vertical<weight=25 text><weight=5><vertical listboxes gap=5><weight=5><weight=25 <>< weight=40% buttons gap=20><>> ><> ><>");
    fm_place["text"] << lbl1;
    fm_place["listboxes"] << list1 << list2;
    fm_place["buttons"] << btn_start << btn_close;
    fm_place.collocate();
	
    fm.show();
    nana::exec();
}

少ないコーディング量でフォーム表示までたどり着けるのは良いですね。
フォームのウィジェットはベタに書いてから整形してますが、他にも方法があります。

データベース接続失敗やクエリ失敗時の例外処理、クリック連打対策等、まだ実装してない部分もありますが、とりあえずの目的は達成できました。

・CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(gui_odbc CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Threads REQUIRED)
find_package(nanodbc REQUIRED)
add_executable(gui_odbc main.cpp)
target_link_libraries(gui_odbc PRIVATE Threads::Threads)
target_link_libraries(gui_odbc PRIVATE nana X11 Xft fontconfig)
target_link_libraries(gui_odbc PRIVATE nanodbc)

雑記

いつもなら Qt5 でコーディングするようなネタですが、今回は QODBC ではなくnanodbc を使ってるので、Qt5 である必要はない。それなら wxWidgets でも...と思いましたが、折角の機会なので、初めての Nana C++ Library に挑戦してみました。

これを選んた理由はいくつかありますが、 GUI プログラミングでありがちな「処理が busy 状態だと描画を妨げる」件について、ドキュメントを見つけたのが大きいかも。

ここに Nana の UI thread と nana::threads::pool について言及されてたので、それを参考にして、コードを書いてみた次第です。

std::threadを利用してnanodbcからクエリを実行する

現在 Linux 環境の ODBC 接続では SQL_ATTR_ASYNC_ENABLE に SQL_ASYNC_ENABLE_ON を設定しても機能しません。※iODBC や商用ドライバは調べてません。
そして nanodbc の async_ 系関数も Linux 環境では実装されてません。
WaitForSingleObject()とか無いもんね。残念です。

その一方で、 Linux 環境の ODBC ドライバでも別スレッドを立ち上げれば SQL 文を並行して実行可能との事。ODBC ドライバ側で直列化されるとしても、です。

非同期実行等がダメでも、それはそれ。
スレッドを立てて全体の処理時間を効率良く短縮できるなら...と、引き続き nanodbc で試してみました。

ソースコード

とりあえずスレッドを 2 つ用意して、クエリを実行してみます。

・main.cpp
#include <iostream>
#include <future>
#include <chrono>
#include <nanodbc/nanodbc.h>

int main()
{
    std::locale::global(std::locale(""));
    std::string conn_str = "Driver={ODBC Driver 17 for SQL Server};Server=localhost;UID=sa;PWD=abcd1234$;Database=my_test_db;";
    auto start = std::chrono::system_clock::now();
    try
    {
        std::cout << "-- 処理開始" << std::endl;
        // thread #1
        auto th1 = std::thread([&conn_str] {
            std::cout << "-- thread #1" << std::endl;
            //std::this_thread::sleep_for(std::chrono::milliseconds(800));
            nanodbc::connection conn(conn_str);
            nanodbc::string sql_text(NANODBC_TEXT("SELECT コード,氏名,入社日 FROM 社員 WHERE コード>199 ORDER BY コード DESC"));
            nanodbc::result rs = nanodbc::execute(conn, sql_text);
            for (const auto& row : rs) {
                auto val_int = row.get<int>("コード");
                auto val_str = row.get<nanodbc::string>("氏名");
                auto val_date = row.get<nanodbc::string>("入社日");
                std::cout << "th#1 " << val_int << " | " << val_str << " | " << val_date << std::endl;
            }
        });
        // thread #2
        auto th2 = std::thread([&conn_str] {
            std::cout << "-- thread #2" << std::endl;
            //std::this_thread::sleep_for(std::chrono::milliseconds(500));
            nanodbc::connection conn(conn_str);
            nanodbc::string sql_text(NANODBC_TEXT("SELECT コード,氏名,入社日 FROM 社員 WHERE コード>199 ORDER BY 入社日 DESC"));
            nanodbc::result rs = nanodbc::execute(conn, sql_text);
            for (const auto& row : rs) {
                auto val_int = row.get<int>("コード");
                auto val_str = row.get<nanodbc::string>("氏名");
                auto val_date = row.get<nanodbc::string>("入社日");
                std::cout << "th#2 " << val_int << " | " << val_str << " | " << val_date << std::endl;
            }
        });
        std::cout << "-- 途中で何かの処理開始" << std::endl;
        std::cout << "-- 処理中 (1秒くらい)" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "-- 途中で何かの処理終了" << std::endl;
        th1.join();
        th2.join();
        std::cout << "-- 処理終了" << std::endl;
    }
    catch (const std::exception& e)
    {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    auto end = std::chrono::system_clock::now();
    std::cout << "経過時間(ミリ秒):"
        << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
        << std::endl;
    return EXIT_SUCCESS;
}

std::thread に渡すラムダ式でキャプチャしているのは接続文字列です。
nanodbc::connection はスレッド内で生成する必要があります。

・CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(odbc_thread CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(nanodbc REQUIRED)
add_executable(odbc_thread main.cpp)
target_link_libraries(odbc_thread PRIVATE pthread nanodbc)

実行結果

-- 処理開始
-- thread #1
-- 途中で何かの処理開始
-- 処理中 (1秒くらい)
-- thread #2
th#1 th#2 307207 |  | 松沢 誠一小川 さよ子 |  | 1994-04-011987-04-01

th#2 210 | 成宮 真紀 | 1991-04-01
th#1 305 | 青木 俊之 | 1988-04-01
th#2 204 | 川村 匡 | 1990-04-01
th#1 304 | 山本 雅治 | 1989-04-01
th#2 304 | 山本 雅治 | 1989-04-01
th#1 210 | 成宮 真紀 | 1991-04-01
th#2 305 | 青木 俊之 | 1988-04-01
th#1 207 | 松沢 誠一 | 1994-04-01
th#2 307 | 小川 さよ子 | 1987-04-01
th#1 204 | 川村 匡 | 1990-04-01
-- 途中で何かの処理終了
-- 処理終了
経過時間(ミリ秒):1000

拍子抜けするくらいあっさりと意図した結果になりました。
スレッドを待機させても大丈夫でした。

この後、ソースを書き換えて、std::vector<std::thread>みたいなコンテナを用意して、1000個くらいpush_back(std::thread(…等としてみましたが問題ありませんでした。

スレッド生成と接続生成、両方のコストを加味してもなかなか良い感じです。

Ubuntu20.10でnanodbcからODBCの対応状況を確認する

Ubuntu というか Linux 環境で RDBMS 、特に SQL ServerODBC 接続していると、ついつい Windows 環境と同等な動作を期待してしまいます。

そもそも前提が異なるので完全互換は期待してません。
それでも、どの程度対応してるのかな?と思い立ったので、C++ ライブラリの nanodbc を利用して確認してみました。

環境

  • OS: Ubuntu 20.10
  • g++ (Ubuntu 10.2.0-13ubuntu1) 10.2.0
  • nanodbc v2.13.0
  • unixODBC 2.3.7

ODBC ドライバ

私の手元にある ODBC ドライバについて調査してみました。

ドライバ SQL_DRIVER_NAME SQL_DRIVER_VER パッケージ
msodbc libmsodbcsql-17.6.so.1.1 17.06.0001 公式サイト
freetds libtdsodbc.so 01.02.0003 tdsodbc
mysql libmyodbc8w.so 08.00.0022 公式サイト
maria libmaodbc.so 03.01.0009 odbc-mariadb
pg psqlodbcw.so 12.02.0000 odbc-postgresql

※表現の都合上、勝手にドライバに略名をつけてます。

調査結果

各ドライバの調査結果の一部を載せておきます。

パラメータ (sqlext.h) msodbc freetds mysql maria pg
SQL_DRIVER_ODBC_VER 03.52 03.50 03.80 03.51 03.51
SQL_ASYNC_MODE 2 0 0 0 0
SQL_ACTIVE_CONNECTIONS 0 0 0 0 0
SQL_ACTIVE_STATEMENTS 1 1 0 0 0
SQL_ODBC_API_CONFORMANCE 2 2 1 1 1
SQL_ODBC_SQL_CONFORMANCE 1 1 1 1 1
SQL_MULT_RESULT_SETS Y Y Y Y Y
SQL_ACTIVE_ENVIRONMENTS 0 0 0 0 0
SQL_SQL_CONFORMANCE 1 1 4 4 1
SQL_MAX_ASYNC_CONCURRENT _STATEMENTS 1 1 0 0 core dump
SQL_ODBC_INTERFACE
_CONFORMANCE
3 2 2 1 1
SQL_ATTR_ODBC_VERSION 380 380 380 380 380
SQL_ATTR_AUTOCOMMIT 1 1 1 4 1

unixODBC の SQL_ODBC_VER は 03.52 です。
SQL_ATTR_ODBC_VERSION が 3 ではなく 380 なのは、nanodbc の設定です。

残念ながら ODBC 3.8 の Driver-Aware Connection Pooling や 非同期通知等は使えないようです。

ソースコード

nanodbc::connection の get_info<T>() を利用すると手軽に情報を取得できます。

get_info<T>() は ODBC API の SQLGetInfo() を呼び出してます。
環境属性や接続属性を調査する場合は、ハンドルが用意されているので、

  • SQLGetEnvAttr() には native_env_handle()
  • SQLGetConnectAttr() には native_dbc_handle()

を指定すると情報を取得できます。

以下は、冗長な部分を省いたコードのサンプルです。

・ソースファイル (main.cpp)

#include <iostream>
#include <sql.h>
#include <sqlext.h>
#include <nanodbc/nanodbc.h>

int main() {
    std::locale::global(std::locale(""));
    std::string conn_str = "Driver={ODBC Driver 17 for SQL Server};Server=localhost;UID=sa;PWD=abcd1234$;Database=my_test_db;";
    //std::string conn_str = "Driver={FreeTDS};ServerName=mssql-server;UID=sa;PWD=abcd1234$;Database=my_test_db;";
    //std::string conn_str = "Driver={MySQL ODBC 8.0 Driver};Server=localhost;UID=dareka;PWD=dareda;Database=my_test_db;";
    //std::string conn_str = "Driver={MariaDB Unicode};Server=localhost;UID=dareka;PWD=dareda;Database=my_test_db;";
    //std::string conn_str = "Driver={PostgreSQL Unicode};Server=localhost;UID=dareka;PWD=dareda;Database=my_test_db;";
    try
    {
        nanodbc::connection conn(conn_str);
        std::cout << "get_info()" << std::endl;
        {
            auto ret_ver = conn.get_info<std::string>(SQL_DRIVER_ODBC_VER);
            std::cout << "-- get_info(SQL_DRIVER_ODBC_VER) --> " << ret_ver << std::endl;
            auto ret_am = conn.get_info<SQLUINTEGER>(SQL_ASYNC_MODE);
            std::cout << "-- get_info(SQL_ASYNC_MODE) --> " << ret_am  << std::endl;
        }
        std::cout << "SQLGetEnvAttr()" << std::endl;
        {
            SQLINTEGER val = 0;
            SQLRETURN ret = SQLGetEnvAttr(conn.native_env_handle(), SQL_ATTR_ODBC_VERSION, &val, sizeof(val), nullptr);
            std::cout << "-- SQL_ATTR_ODBC_VERSION : ret = " << ret << " / val = "  << val  << std::endl;
        }
        std::cout << "SQLGetConnectAttr()" << std::endl;
        {
            SQLINTEGER val = 0;
            SQLRETURN ret = SQLGetConnectAttr(conn.native_dbc_handle(), SQL_ATTR_ASYNC_ENABLE, &val, sizeof(val), nullptr);
            std::cout << "-- SQL_ATTR_ASYNC_ENABLE : ret = " << ret << " / val = "  << val  << std::endl;
        }
    }
    catch (const std::exception& e)
    {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

・CMakeLists.txt

cmake_minimum_required(VERSION 3.0)
project(odbc_getinfo CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(nanodbc REQUIRED)
add_executable(odbc_getinfo main.cpp)
target_link_libraries(odbc_getinfo PRIVATE pthread nanodbc)

所感 

当初、昔を思い出しながら SQLAllocHandle() で環境ハンドル用意して、SQLSetEnvAttr() で ODBC バージョン指定して、SQLAllocHandle() で接続ハンドル用意して...と、C で簡単なプログラムを書いて環境を調べました。
とりあえず目的は達成できたのですが、煩雑すぎて不満が募ります。

そこで、以前調査した nanodbc を使ってみたのですが、やっぱりお手軽です。
Poco や Qt5 より軽量と言うか薄い分、Win32API っぽいコーディングはやりやすいかも。

Ubuntu20.10でMarble Mouseの設定方法が変わってた

Ubuntu 20.10 がリリースされたので、早々に OS をアップグレードしました。

今回はクリーンインストールではなく、久しぶりのアップグレードです。
事前に躓きそうなパッケージを remove しておいたので割とスムーズに完了しました。

リポジトリを調整して、各種パッケージの環境を整えて作業完了。
さて、いつもの作業に戻ろうか...

なんて思ってたら Marble Mouse のスクロール機能が有効になってません!
ターミナルから確認しても、

$ echo $XDG_SESSION_TYPE
x11
$ xinput list
⎡ Virtual core pointer                    	id=2	[master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer              	id=4	[slave  pointer  (2)]
⎜   ↳ Logitech USB Trackball                  	id=8	[slave  pointer  (2)]
 〜 (ざっくり省略) 〜

セッションは Xorg のままだし、xinput からも見えてます。

Ubuntu 20.04 LTS では、上記のサイトに従い libinput 用の構成ファイルを /usr/share/X11/xorg.conf.d/ に配置してましたが、それも消えずに残ってました。

....

このままでは困るので、ダメ元で Wayland での設定方法を試してみました。

$ gsettings set org.gnome.desktop.peripherals.trackball scroll-wheel-emulation-button 9

バッチリです!即時反映されるので、すぐにスクロール機能が有効になりました。

今までは Xorg と Wayland で設定方法が異なり、少々煩わしかったのですが、gsettings に統一されて良かったです。

....

GNOME 3.38 ベースになって、色々と細かいところが改善されてますね。

ちなみに Kubuntu 20.10 で確認したところ、こちらの設定方法は今まで通りでした。