Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

gtkmmでのキーボード入力(キーイベント検知)

前回までgtkmmについてまとめてきました。
elsammit-beginnerblg.hatenablog.com
elsammit-beginnerblg.hatenablog.com
elsammit-beginnerblg.hatenablog.com

今回はgtkmmでキーイベントを検知し、
キーボードからの入力を可能にする方法をまとめていきます!!



■実は、、、

gtkmmにはキーイベントを検知するためのAPIが用意されておりません。。。泣
このため、gtkmmでのGUIにてキーボード入力に対応するためには、、
別途キーイベントを検知することが出来る処理を自前で用意する必要があります。

さらにさらに、、、
Windowsの場合、キーイベントはkbhit関数(最新は_kbhit ??)があり、こちらのAPIを用いれば簡単に実装出来ますが、
Linuxの場合、Windowsのようなkbhit関数は用意されていません。。泣

■キーボードイベント

よって、Linuxにてgtkmmでキーイベントを検知したい場合には自前でkbhit関数のような処理を実装する必要があります!!
ではLinuxでのkbhit関数を実装していきたいのですが、すでに実装された方がいらっしゃったので、こちらを流用させて頂こうと思います!!
参考にしたサイトはこちらになります↓↓
https://hotnews8.net/programming/tricky-code/c-code03

こちらのkbhit関数ですが、windowsと同様に
キーイベントを検知しない場合には0が返ってきて、
何かしらのキーイベントを検知した場合、検知したキー番号を返す関数になっております。

■kbhit関数を用いたgtkmmのキーボード入力

では本題のgtkmmでのキーボード入力です。

実装したコードはこんな感じになります。

#include <iostream>
#include <gtkmm.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <termios.h>
#include <fcntl.h>
#include <thread>

class MainWin : public Gtk::Window {
    Gtk::Button *btnClick;
    Gtk::Label *selectlabel1, *selectlabel2, *selectlabel3;
    Glib::RefPtr<Gtk::Builder> builder;

public:
    MainWin(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade);
    virtual ~MainWin();
    void SetLabel(int keyEvent);
};


MainWin *mainWin = nullptr;
int select_flg = 0;

MainWin::MainWin(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade) :
    Gtk::Window(cobject), builder(refGlade) {
    this->builder->get_widget("selectlabel1", this->selectlabel1);
    this->builder->get_widget("selectlabel2", this->selectlabel2);
    this->builder->get_widget("selectlabel3", this->selectlabel3);
}

MainWin::~MainWin(){
}

int kbhit(void)
{
    struct termios oldt, newt;
    int ch;
    int oldf;

    tcgetattr(STDIN_FILENO, &oldt);
    newt = oldt;
    newt.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
    oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);

    ch = getchar();

    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    fcntl(STDIN_FILENO, F_SETFL, oldf);

    if (ch != EOF)
    {
        ungetc(ch, stdin);
        return 1;
    }

    return 0;
}

void KeyBoardInterupt(){
    printf("Call KeyBoardInterupt \n");
    printf("test is %d \n",mainWin->test);
	int Keyevent;
	while(1)
	{
		if(kbhit()){
			Keyevent = getchar();
			mainWin->SetLabel(Keyevent);
		}
        sleep(1);
	}
}

void MainWin::SetLabel(int keyEvent){
    printf("select_flg:%d \n",select_flg);
	if(keyEvent == 66){
		switch(select_flg){
			case 0:
				this->selectlabel1->set_text("");
				this->selectlabel2->set_text(">");
				select_flg++;
				break;
			case 1:
				this->selectlabel2->set_text("");
				this->selectlabel3->set_text(">");
				select_flg++;
				break;
			case 2:
				this->selectlabel3->set_text("");
				this->selectlabel1->set_text(">");
				select_flg = 0;
				break;
			default:
				break;
		}
    }
}

int main(int argc, char** argv) {
    Gtk::Main app(argc, argv);

    Glib::RefPtr<Gtk::Builder> builder = Gtk::Builder::create_from_file("MainWIndow.glade"); 

     builder->get_widget_derived("MainWindow",mainWin); 
     std::thread KeyEventThread = std::thread(KeyBoardInterupt);
     Gtk::Main::run(*mainWin);

     KeyEventThread.join();
}

ちょっと長くなってしまっていますが、、、
やっていることは結構簡単です。
まず、画面表示とは別にKeyBoardInteruptスレッドを生成。

std::thread KeyEventThread = std::thread(KeyBoardInterupt);

KeyBoardInteruptスレッドは、

void KeyBoardInterupt(){
	while(1)
	{
		if(kbhit()){
			Keyevent = getchar();
			mainWin->SetLabel(Keyevent);
		}
        sleep(1);
	}
}

で定義していまして、入力されたきーイベントをkbhitで1秒ごとに検知する処理になっております。
もしキーイベントが発行されれば、キーイベント番号を引数にSetLabel(Keyevent)をコールします。
SetLabel(Keyevent)は、

void MainWin::SetLabel(int keyEvent){
    printf("select_flg:%d \n",select_flg);
	if(keyEvent == 66){
		switch(select_flg){
			case 0:
				this->selectlabel1->set_text("");
				this->selectlabel2->set_text(">");
				select_flg++;
				break;
			case 1:
				this->selectlabel2->set_text("");
				this->selectlabel3->set_text(">");
				select_flg++;
				break;
			case 2:
				this->selectlabel3->set_text("");
				this->selectlabel1->set_text(">");
				select_flg = 0;
				break;
			default:
				break;
		}
    }
}

で、keyEvent == 66の場合(今回はキーボードの↓ボタン)に>マークを動かす処理にしております。

■実際に動かしてみる

では、先ほどのコードを動かしてみます。
結果はこんな感じになります。
(キーボードを押しておりますが取れていない。。。)
f:id:Elsammit:20210113210050g:plain

■最後に

今回はgtkmmでのキーイベントについてまとめてみました。
今までの内容を組み焦れば、大体のGUI制御はできますね!!
これからアプリでも作ってみようかな🤔