(ArchLinux)(AlterLinux)(xmodmap) キーボードの、あるキーを押した時に、それとは違うキーを押したことにしたい

この記事はAlterLinuxPlasma版(ArchLinux系)で確認したものになります

一例としてこういうケースが有ったとします、
> よくスクリーンショットを取るけど PrintScreenキーが配置的になんか煩わしいので、
> 普段使わず浮いてる無変換キーを押したらスクリーンショットが発動して欲しい。
あくまで一例ですので似たようなケースに置き換えて読んでください。

実現する方法の一つとして「グローバルショートカットキーとして仕込む」というものがありますが、
キーによっては発動しない場合があるようです、
個人的環境(AlterLinux Plasma版)では、無変換キー単体にコマンドを仕込むことは出来ませんでした。

こういった場合にどうするのか?というと、
無変換キーを押したらPrintScreenキーを押したことにする
と、いうことになります、
ただ、それを実現するには xmodmapを使用することになりますが、
まずはキー入力の仕組みを知らないと、設定で混乱することになりますので少し大雑把に解説します。
例えば、 キーボード上の「A」と印刷されているキーを押すと、
OSはその物理キーに割り当てられている番号をキーコードとして認識します
そして、そのキーコードに対して割り当てられている 「a」を表すキーシムに置き換えて、
aが押されたと認識されることになります。
(かなり大雑把な説明なので、とりあえずはどんな感じかだけ把握してください)

よって、今回のケースで言えば、
無変換キーのキーコードに対して、「PrintScreenキーを表すキーシム」を割り当てる
ということをやれば、「無変換」を押しても「PrintScreen」を押した事になります。

ここで少し回り道になりますが、
あるキーを押した時にOSが認識している キーコードとキーシムの調べ方 について解説します、
厄介なのが、キーボードやOS環境によってキーコードとキーシムが必ずしも同じではなくて
どの環境ででも○キーのキーコードは○○で、キーシムは○○になるとは言いきれません、
よって、各自で実際にキーを押してみて、どう認識されているかを調べたほうが確実になります、

調べるにはGUIで動作する簡単なツール xev(extra/xorg-xev) があります、
ターミナルから xev として起動して、アクティブ時に調べたいキーを押すだけです
私の環境で Aと印字されたキーを押した場合、以下のようにレポートされました、

KeyPress event, serial 40, synthetic NO, window 0x3e00001,
    root 0x6b5, subw 0x0, time 694366, (462,199), root:(1333,664),
    state 0x10, keycode 38 (keysym 0x61, a), same_screen YES,
    XLookupString gives 1 bytes: (61) "a"
    XmbLookupString gives 1 bytes: (61) "a"
    XFilterEvent returns: False

KeyRelease event, serial 40, synthetic NO, window 0x3e00001,
    root 0x6b5, subw 0x0, time 694448, (462,199), root:(1333,664),
    state 0x10, keycode 38 (keysym 0x61, a), same_screen YES,
    XLookupString gives 1 bytes: (61) "a"
    XFilterEvent returns: False

今回注目すべき部分は keycode 38 (keysym 0x61, a) の所ですね、
どうやら「Aと印字されたキー」の「キーコード は 38」で「キーシム は 0x61(a)」となるようです
他にも色々試してると NumLock Shift Ctrl などによって stateの値が 変わっているようです、
ちなみに、Shift + Aと印字されたキーの場合だと

KeyPress event, serial 40, synthetic NO, window 0x3e00001,
    root 0x6b5, subw 0x0, time 1052924, (363,8), root:(1234,473),
    state 0x11, keycode 38 (keysym 0x41, A), same_screen YES,
    XLookupString gives 1 bytes: (41) "A"
    XmbLookupString gives 1 bytes: (41) "A"
    XFilterEvent returns: False

KeyRelease event, serial 40, synthetic NO, window 0x3e00001,
    root 0x6b5, subw 0x0, time 1053024, (363,8), root:(1234,473),
    state 0x11, keycode 38 (keysym 0x41, A), same_screen YES,
    XLookupString gives 1 bytes: (41) "A"
    XFilterEvent returns: False

キーコードは同じく38ですが、キーシムは 0x41(A)に変わっていますね
つまり、キーコードが同じでも Shiftなどと組み合わせると キーシムが変わることが有るようですね。

なんとなく分かってきた所で、無変換キー と PrintScreenキー も同じように調べてみます

KeyPress event, serial 40, synthetic NO, window 0x3e00001,
    root 0x6b5, subw 0x0, time 1726706, (468,287), root:(1339,752),
    state 0x10, keycode 102 (keysym 0xff22, Muhenkan), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyRelease event, serial 40, synthetic NO, window 0x3e00001,
    root 0x6b5, subw 0x0, time 1726826, (468,287), root:(1339,752),
    state 0x10, keycode 102 (keysym 0xff22, Muhenkan), same_screen YES,
    XLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyPress event, serial 40, synthetic NO, window 0x3e00001,
    root 0x6b5, subw 0x0, time 1728572, (468,287), root:(1339,752),
    state 0x10, keycode 107 (keysym 0xff61, Print), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyRelease event, serial 40, synthetic NO, window 0x3e00001,
    root 0x6b5, subw 0x0, time 1728692, (468,287), root:(1339,752),
    state 0x10, keycode 107 (keysym 0xff61, Print), same_screen YES,
    XLookupString gives 0 bytes: 
    XFilterEvent returns: False

無変換キーは、キーコードが102、キーシムが0xff22(Muhenkan)
PrintScreenキーは、キーコードが107、キーシムが0xff61(Print)
であることが分かりましたね。

ということで 本来の目的である、無変換キーを押したらPrintScreenを押したことにする事とは、
キーコード102 に キーシム0xff61(Print) を 割り当てればよさそうです
xmodmapの使い方を見て、コマンドを打ってみます

xmodmap -e "keycode 102 = 0xff61"
#または
xmodmap -e "keycode 102 = Print"

再び xevで確認してみます

KeyPress event, serial 40, synthetic NO, window 0x3c00001,
    root 0x6b5, subw 0x0, time 2972834, (315,336), root:(1186,801),
    state 0x10, keycode 102 (keysym 0xff61, Print), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyRelease event, serial 40, synthetic NO, window 0x3c00001,
    root 0x6b5, subw 0x0, time 2972956, (315,336), root:(1186,801),
    state 0x10, keycode 102 (keysym 0xff61, Print), same_screen YES,
    XLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyPress event, serial 40, synthetic NO, window 0x3c00001,
    root 0x6b5, subw 0x0, time 2979404, (163,-27), root:(1034,438),
    state 0x10, keycode 107 (keysym 0xff61, Print), same_screen YES,
    XKeysymToKeycode returns keycode: 102
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyRelease event, serial 40, synthetic NO, window 0x3c00001,
    root 0x6b5, subw 0x0, time 2979506, (163,-27), root:(1034,438),
    state 0x10, keycode 107 (keysym 0xff61, Print), same_screen YES,
    XKeysymToKeycode returns keycode: 102
    XLookupString gives 0 bytes: 
    XFilterEvent returns: False

キーコード 102(無変換キー) も 107(PrintScreenキー) も、両方ともキーシムが 0xff61(Print) になってます。

ただし、コマンドによって割り振られたものは一時的なもので、PCを再起動したりすると元に戻ります、
この設定を永続化させるためには ~/.Xmodmap に設定します、ファイルが無い場合は作成します、

! この行はコメント行
keycode 102 = Print

~/.Xmodmap を編集したらPCを再起動して ログイン時から反映していれば成功です。

AlterLinuxPlasma版の場合 PrintScreenキーを押してもスクリーンショットは発動しないようで、
個人的には グローバルショートカットに ImageMagickのimportを使ってスクショを撮る自作のソフトを仕込み、
PrintScreenキーを押す度に それを呼び出すようにしています、
無変換キーにも同じように グローバルショートカットを仕込みましたが、
特殊な扱いなのか、いくら押しても実行されませんでした、そこで試してみたのが今回のやり方ですが、
これならきちんとPrintScreenキーが押された判定となり、スクショが発動するようになりました。

(Crystal)(PNG) CrystalでPNG画像の幅と高さをヘッダの部分から取得する

Crystalで PNG画像ファイルの幅と高さをヘッダの部分から取得します、
ヘッダ部分を見て、PNG画像のヘッダかどうかの判断もするようになっています、
実際に画像データを読み込むわけではないのでデータが壊れていないなどの保証は出来ません、
その分、情報の取得速度は非常に高速です。
ヘッダの構造を読み替えれば、その他の情報や その他のファイル形式にも応用できます。

# png_picture_size <filename> [0|1] [both|cols|rows]
# 0 or 1  カウントを始める数、省略時は 1 
# (座標として扱う時は0、 幅や高さとして扱う時は1の方が扱いやすい)
# cols 幅だけ、rows 高さだけ、省略 または both 両方。
# 0, 1 および both, cols, rows を複数指定した場合は 最後のものが有効
# (例:「png_picture_size ./data.png both cols 0 rows 1」は「rows と 1」のオプションが有効)
# 取得に成功した時は 標準出力で数値を返します (幅高さ両方の時は 半角スペース区切りです)
# 取得に失敗した時は ERRORLEVEL に 1を設定するのでそれを参照するか
# 標準出力に 長さ0の文字列 "" を返すのでそれで判断してください
(
  #puts("no option")
  exit(1)
) if ARGV.size < 1
png_file = File.expand_path(ARGV[0])
(
  #puts("file not found")
  exit(1)
) if !File.exists?(png_file)
count_decrement = 0
cols_rows = 0 # 0=両方 1=colsのみ 2=rowsのみ
(1..(ARGV.size-1)).each{|c|
  case ARGV[c]
    when "0" then
      count_decrement = 1
    when "1" then
      count_decrement = 0
    when "both" then
      cols_rows = 0
    when "cols" then
      cols_rows = 1
    when "rows" then
      cols_rows = 2
  end
}
# 画像の幅や高さの情報は IHDRチャンク内にあり場所は固定なので、その部分を取得すれば値を得られます
# PNGファイルシグネチャ   8byte [0..7] -> 0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A ならPNG形式
# IHDRチャンク     25byte  [8..32]
#   Length         4byte  -> ChunkDataLength   0x0d (13)で固定   [8..11]
#   ChunkType      4byte  -> 0x49 0x48 0x44 0x52 (IHDRのASCIIコード)で固定   [12..15]
#   ChunkData      4byte  -> Width   [16..19]
#   ChunkData      4byte  -> Height   [20..23]
#   ChunkData      1byte  -> BitDepth   [24]
#   ChunkData      1byte  -> ColorType   [25]
#   ChunkData      1byte  -> Compression   [26]
#   ChunkData      1byte  -> Filter   [27]
#   ChunkData      1byte  -> Interlace   [28]
#   CRC            4byte  -> ChunkTypeとChunkDataから算出される   [29..32]
# 以降、IDATチャンク IENDチャンクが続く
File.open(png_file){|f|
  begin
    bin = Slice(UInt8).new(24)
    f.read(bin)
    exit(1) if bin[0..7].hexstring != "89504e470d0a1a0a"
    width = bin[16..19].hexstring.to_i(16)
    height = bin[20..23].hexstring.to_i(16)
    print(
      case cols_rows
        when 0
          "#{width-count_decrement} #{height-count_decrement}"
        when 1
          "#{width-count_decrement}"
        when 2
          "#{height-count_decrement}"
      end
    )
  rescue
    #puts("some kind of error has occurred")
    exit(1)
  end
}

(Crystal)(ArchLinux)(AlterLinux) --staticでbuildする時に「ライブラリが見つからない」となる場合

この記事はAlterLinux(ArchLinux系)で確認したものになります

Crystalで 動的リンクではなく静的リンクで外部のライブラリに依存することなく動作させようとして
--staticオプションを付けて buildしようとしたら -lpcre -levent -lgc 等が見つからないとなって、
buildに失敗することがあります。

原因としては 静的リンクライブラリファイル(.a)が無いからのようで、
何らかの方法で不足しているファイルを用意すればエラーは出なくなるようです。

基本的な流れは ソースからビルドを行い ライブラリとしてパスが通った所にインストールする感じですが、
make installまでやってしまうとシステム全体との整合性が取れなくなってしまう可能性もゼロでは無いかと思い、
試しに不足している 各 .a ファイルのみを /usr/local/lib/ に送り込んでみたところ、
--staticでの buildが成功するようになりました。

今後、もしも 突然 --staticでのbuildが出来なくなった場合などは
最新バージョン 過去のバージョン システムと同じバージョン などを試すと 上手く行くかもしれません。

バージョン部分に関しては x に置き換えてますので各DLページにて最新版のものに読み替えてください。
不足分の .a ファイルをインストールする方法を確認できたら 今後も追記していきます。


-lpcre(libpcre.a) が 無い場合

# https://www.pcre.org/ から ソースファイルを取得してビルドし、/usr/local/lib/ に 送り込みます。
# pcre2(GitHubのほう)ではなく、 pcre(SourceForgeのほう)のソースを取得してください。
# (バージョンは最新ので問題ないかと思います)
wget https://sourceforge.net/projects/pcre/files/pcre/x.xx/pcre-x.xx.tar.bz2/download -O pcre.tar.bz2
tar -jxvf pcre.tar.bz2
cd pcre-x.xx
./configure
make
sudo cp ./.libs/libpcre.a /usr/local/lib/libpcre.a



-levent(libevent.a) が 無い場合

# https://libevent.org/ から ソースファイルを取得してビルドし、/usr/local/lib/ に 送り込みます。
# (バージョンは最新ので問題ないかと思います)
wget https://github.com/libevent/libevent/releases/download/release-x.x.xx-stable/libevent-x.x.xx-stable.tar.gz
tar -zxvf libevent-x.x.xx-stable.tar.gz
cd libevent-x.x.xx-stable
./configure
make
sudo cp ./.libs/libevent.a /usr/local/lib/libevent.a



-lgc(libgc.a) が 無い場合

# https://www.hboehm.info/gc/ から ソースファイルを取得してビルドし、/usr/local/lib/ に 送り込みます。
# (バージョンは最新ので問題ないかと思います)
# --enable-staticオプションを付けることで静的ライブラリもビルドされるようです
wget https://www.hboehm.info/gc/gc_source/gc-x.x.x.tar.gz
tar -zxvf gc-x.x.x.tar.gz
cd gc-x.x.x
./configure '--enable-static'
make
sudo cp .libs/libgc.a /usr/local/lib/libgc.a



-lssl(libssl.a) と -lcrypto(libcrypto.a) が 無い場合

# libssl.a と libcrypto.a は opensslに含まれているので opensslを ビルドし、/usr/local/lib/ に 送り込みます。
# https://www.openssl.org/source/ から openssl-1.x.x.tar.gz を取得します
# バージョンは 1と3の系統がありますが バージョン1系統の 最新バージョンを使用してみました。
wget https://www.openssl.org/source/openssl-1.x.x.tar.gz
tar -zxvf openssl-1.x.x
cd openssl-1.x.x
./config    # ./configureではないので注意
make
sudo cp ./libcrypto.a /usr/local/lib/libcrypto.a
sudo cp ./libssl.a /usr/local/lib/libssl.a

(gcc)(C++)マングリングされた文字列をデマングリングする

データの型を調べようとして typeid().name()をやっても、簡潔な文字列しか返って来ません
i は int となんとなく分かりますが、 x の long longy の unsigned long long は 知らないともう想像も付きません、
この簡潔な文字を デマングリングすることで こちらが期待している文字列を取得することが出来ます、
以下に デマングリングのサンプルを書いておきます。

#include <cxxabi.h>
#include <cstdlib>
#include <iostream>
// 使用する場合は .cppファイルと同じディレクトリに この内容を demangle.h として置いて
// #include <"./demangle.h">
// を .cppファイルに追加してください。
// 引数には typeid(<調べたい値>).name() もしくは "PKc"のようにマングリングされた文字列 を入れてください
// 戻り値は デマングリング文字列が std::stringで返ってきます。
std::string demangle(char const* mangle)
{
  int status = 1;
  char* dem = abi::__cxa_demangle(mangle, 0, 0, &status);
  std::string ret;
  if (status == 0)
  {
    ret = std::string(dem);
  } else {
    ret = std::string("");
  }
  std::free(dem);
  return ret;
}

(ruby)(crystal) rubyとcrystalで違う部分 「コマンドライン引数」

rubyとほぼ同じような書き方が出来るcrystalですが、
当然違う言語なので違う部分も色々とあります、
その一つとして コマンドライン引数の受け方で違いが出てきます。

rubyで 第一引数が無かった場合の判定としては ARGV[0] == nil と書くことが出来ますが、
crystalでは 引数がない所の値を取得しようとすると、Index out of boundsエラーが 発生します、
rubyでは引数が無い部分を指定したらnilが返ってきますが、crystalではそうなりません。

(
  puts("ファイルを指定してください")
  exit(1)
) if ARGV[0] == nil # コマンドライン引数を書かなかった場合、rubyだけしか正常に動かない


crystalで 第○引数の有無の判定は、引数の数がいくつあるかを見て判断することになります、
この書き方なら rubyでも動作します。

(
  puts("ファイルを指定してください")
  exit(1)
) if ARGV.size == 0 # コマンドライン引数を書かなかった場合でも crystalも rubyも 正常に動く
puts(ARGV[0]) # 第一引数がきちんとある状態なら crystalでも ARGV[0]で受け取ることが出来る