Привет, хабр! Сегодня мы создадим свой собственный терминал на языке программирования C++. Я являюсь фанатом Linux, и пользуюсь минималистичными терминальными программами — от Vim как IDE до чатов. Есть множество терминалов, у каждого из них есть плюсы и минусы. Наш терминал не будет претендовать на место серьезного проекта, но если вы хотите улучшить код, который мы сегодня напишем — то вы молодцы, можете без проблем развить наш терминал.

Это будет небольшой, минималистичный терминал для Linux. Он будет на основе фреймворка Qt 5 и библиотеки qtermwidget5

Не буду долго тянуть, вперед! Исходный код будет в моем репозитории.

Что нам сегодня будет нужно:

  • Компилятор gcc (G++)

  • Make, QMake

  • Qt5-base

  • qtermwidget5

  • build‑devel/essential


Итак, для начала нам нужно установить все зависимости, которые я привел сверху.

# Arch
sudo pacman -Sy qtermwidget basedevel qt5-base make gcc
# Для других дистрибутивов смотрите в их репозиториях

Основа нашего терминала - Qt-виджет qtermwidget.

Работающий минимум

Для начала создаем проект:

qmake -project

После в файл <названиеПроекта>.pro помещаем следующий код (не забудьте заменить имена):

######################################################################
# Automatically generated by qmake (3.1) Mon Nov 27 18:10:42 2023
######################################################################

TEMPLATE = app
TARGET = НАЗВАНИЕ
INCLUDEPATH += .
INCLUDEPATH += /usr/include/qtermwidget5

# You can make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# Please consult the documentation of the deprecated API in order to know
# how to port your code away from it.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

# Input
SOURCES += src/исходныйКод.cpp
TEMPLATE += app
QT += gui widgets
unix:!macx: LIBS += -lqtermwidget5

Дальше при помощи qmake мы создаем Makefile:

qmake -makefile

И здесь мы создаем файл кода С++ и пишем в него следующее:

#include <QApplication>
#include <QMainWindow>

#include "qtermwidget.h"

int main(int argc, char *argv[]) {
  // Создаем окно и приложение
  QApplication app(argc, argv);
  QMainWindow *mainWindow = new QMainWindow();

  // Создаем объект консоли
  QTermWidget *console = new QTermWidget();

  // Добавляем шрифт терминала
  QFont font = QApplication::font();
  font.setFamily("Monospace"); // задаем шрифт monospace
  font.setPointSize(14); // задаем размер шрифта в pt

  console->setTerminalFont(font); // задаем наши характеристики шрифта в консоль

  // Показ окна
  QObject::connect(console, SIGNAL(finished()), mainWindow, SLOT(close()));
  mainWindow->setCentralWidget(console);
  mainWindow->show();

  return app.exec();
}

Мы включаем нужные нам библиотеки, создаем приложение и создаем виджет терминала. После мы задаем ему шрифт. В конце подключаем терминал и главное окно (то есть закрытие одного окна/виджета заставит закрыться другое окно/виджет), а после показываем окно.

Компиляция

На протяжении всего туториала — компиляция будет состоять из одной строчки:

make

Цветовые схемы

Добавим немного цветов в наш терминал.

#include <QApplication>
#include <QMainWindow>

#include "qtermwidget.h"

int main(int argc, char *argv[]) {
  // Создаем окно и приложение
  QApplication app(argc, argv);
  QMainWindow *mainWindow = new QMainWindow();

  // Создаем объект консоли
  QTermWidget *console = new QTermWidget();

  // Добавляем шрифт терминала
  QFont font = QApplication::font();
  font.setFamily("Monospace"); // задаем шрифт monospace
  font.setPointSize(14); // задаем размер шрифта в pt

  console->setTerminalFont(font); // задаем наши характеристики шрифта в консоль

  // Задаем цветовую схему. Существующие можно посмотреть в /usr/share/qtermwidget5/color-schemes
  console->setColorScheme("Tango");

  // Показ окна
  QObject::connect(console, SIGNAL(finished()), mainWindow, SLOT(close()));
  mainWindow->setCentralWidget(console);
  mainWindow->show();

  return app.exec();
}

Мы добавляем в наш предыдущий код всего одну функцию — console->setColorScheme("Tango");

Эта волшебная функция заставляет принять наш терминал цветовую схему Tango (остальные можно посмотреть в /usr/share/qtermwidget5/color‑schemes/

Копипаста!

Что‑же за терминал такой, в котором даже не работает копирование и вставка текста? Давайте это исправим. Мы добавим кейбинды!

#include <QApplication>
#include <QMainWindow>
#include <QKeySequence> // добавляем новую библиотеку
#include "qtermwidget.h"

int main(int argc, char *argv[]) {
  // Создаем окно и приложение
  QApplication app(argc, argv);
  QMainWindow *mainWindow = new QMainWindow();

  // Создаем объект консоли
  QTermWidget *console = new QTermWidget();

  // Добавляем шрифт терминала
  QFont font = QApplication::font();
  font.setFamily("Monospace"); // задаем шрифт monospace
  font.setPointSize(14); // задаем размер шрифта в pt

  console->setTerminalFont(font); // задаем наши характеристики шрифта в консоль

  // Задаем цветовую схему. Существующие можно посмотреть в /usr/share/qtermwidget5/color-schemes
  console->setColorScheme("Tango");

  // Подключаем
  QObject::connect(console, &QTermWidget::termKeyPressed, mainWindow,
					[=](const QKeyEvent *key) -> void { // проверяем нажатия клавиш
						if (key->matches(QKeySequence::Copy)) {
							console->copyClipboard(); // ctrl+c
						}
					});

  // Показ окна
  QObject::connect(console, SIGNAL(finished()), mainWindow, SLOT(close()));
  mainWindow->setCentralWidget(console);
  mainWindow->show();

  return app.exec();
}

Мы получаем нажатия клавиш, и если они равны ctrl+c (копирование) или ctrl+v (вставка) мы копируем или вставляем текст. Но есть интересная фича - если мы сделаем кейбинд вставки текста, то текст будет вставляться два раза. Поэтому мы не пишем кейбинд, т.к. вставка текста работвет по умолчанию, в отличии от копирования.

Ссылки, ссылки, ссылки...

Давайте создадим еще несколько "плюшек" - возможность активации ссылок, моргающий курсор и внутренние отступы!

#include <QApplication>
#include <QMainWindow>
#include <QDesktopServices> // добавляем новую библиотеку
#include <QKeySequence>
#include "qtermwidget.h"

void activateLink(const QUrl &url, bool fromContextMenu) {
  /* Функция, которая ничего не возвращает, принимает на вход URL и 
  fromContextMenu.
  Открывает ссылку в доступном браузере */
  if (QApplication::keyboardModifiers() & Qt::ControlModifier ||
        fromContextMenu) {
    QDesktopServices::openUrl(url);
  }
}

int main(int argc, char *argv[]) {
  // Создаем окно и приложение
  QApplication app(argc, argv);
  QMainWindow *mainWindow = new QMainWindow();

  // Создаем объект консоли
  QTermWidget *console = new QTermWidget();

  // Добавляем шрифт терминала
  QFont font = QApplication::font();
  font.setFamily("Monospace"); // задаем шрифт monospace
  font.setPointSize(14); // задаем размер шрифта в pt

  console->setTerminalFont(font); // задаем наши характеристики шрифта в консоль
  console->setBlinkingCursor(true); // мерцающий курсор
  // Добавляем внутренние отступы 10 пикселей
  console->setMargin(10);

  // Задаем цветовую схему. Существующие можно посмотреть в /usr/share/qtermwidget5/color-schemes
  console->setColorScheme("Tango");

  // Подключаем
  QObject::connect(console, &QTermWidget::termKeyPressed, mainWindow,
					[=](const QKeyEvent *key) -> void { // проверяем нажатия клавиш
						if (key->matches(QKeySequence::Copy)) {
							console->copyClipboard(); // ctrl+c
						}
					});
  // Подключаем функцию активации ссылки
  QObject::connect(console, &QTermWidget::urlActivated, mainWindow,
					activateLink);
  
  // Показ окна
  QObject::connect(console, SIGNAL(finished()), mainWindow, SLOT(close()));
  mainWindow->setCentralWidget(console);
  mainWindow->show();

  return app.exec();
}

И компилируя мы получаем...

Вот такой у нас получился терминал. Я использую на скриншотах шрифт не Monospace, а Iosevka Nerd Fonts.

Заключение

В этой части мы реализовали базовые возможности нашего терминала. В следующей части мы создадим больше плюшек — подумаем над созданием конфигуратора, добавим больше функций.

Спасибо за прочтение! Надеюсь вам понравилось!

Комментарии (22)


  1. profFortran
    27.11.2023 15:57
    -3

    Эм... А чем Вас не устроили существующие терминалы? И почему именно Qt, который из РФ фиг достанешь, а не, например, GTK? С этого стоило бы начать.

    Велосипедостроение, конечно, увлекательная отрасль промышленности, но всё же...


    1. DrArgentum Автор
      27.11.2023 15:57
      +5

      1. Qt мне более известен, а gtk нет.

      2. Меня устраивают все терминалы, но всегда было интересно, как создать его.

      3. В линуксе qt можно установить из любой страны


      1. olartamonov
        27.11.2023 15:57
        +7

        «Создать»? Так вы ничего не создали. Вы готовую библиотечку вызвали.


        1. DrArgentum Автор
          27.11.2023 15:57

          это да, но хотелось для начала изучить основу


          1. vk6677
            27.11.2023 15:57
            +2

            Основа в данном случае - это ОС, системные вызовы.


      1. syrus_the_virus
        27.11.2023 15:57
        +2

        Если интересно узнать, как создать терминал, стоит начать изучать такие вещи, как tty, pts, pipes и при зачем тут coreutils. QT классный, я сам его давно использую, но он скрывает под капотом многие вещи, чем неплохо упрощает жизнь, но что никак не улучшает понимание.


    1. xhd
      27.11.2023 15:57
      +2

      Справедливости ради - исходники Qt вполне доступны, а если, например, компилировать в статические приложения, так и вовсе необходимы


    1. Johan_Palych
      27.11.2023 15:57
      +2

      Qt, который из РФ фиг достанешь

      Ставится из официальных реп.
      https://wiki.archlinux.org/title/Qt
      https://wiki.archlinux.org/title/Qt_(Русский)


      1. LeonidPr
        27.11.2023 15:57

        Набрел на проксю недавно, для установки qt с официальных репозиториев

        https://quterussia.ru/download/

        Может пригодится


        1. Johan_Palych
          27.11.2023 15:57

          Под Linux точно не нужно. Собирают во всех основных дистрибах
          https://pkgs.org/search/?q=qtcreator
          Под Windows можно поставить:
          https://wiki.qt.io/MSYS2
          https://www.msys2.org/
          https://packages.msys2.org/package/mingw-w64-x86_64-qt-creator?repo=mingw64
          или через wsl2


    1. me21
      27.11.2023 15:57
      +1

      Ну почему фиг достанешь. В официальных репозиториях дистрибутивов Линукса он есть. Для Windows есть собранные библиотеки в MSYS2. Из исходников, наконец, собрать можно.


      1. DungeonLords
        27.11.2023 15:57

        А кто уже попробовал альтернативный мультиплатформенный доставщик Qt?


  1. Rusrst
    27.11.2023 15:57

    Примеры это всегда хорошо :)

    Может подскажете как в qt реализуется паттерн mvvm, MVC, MVP? Начал изучать qt и удивился что там эти подходы почти не используются - есть компоненты QML, кастомные контроллы на C++, и все творится в них. А я привык что логика вынесена в другие места. Не осветите этот момент?


    1. apro
      27.11.2023 15:57

      1. Rusrst
        27.11.2023 15:57

        Я может не верно понял, но все примеры, даже с этой страницы, работают лишь с определенными классами view (widget), а я хотел бы создать модель которая инкапсулирует логику целой страницы - т.е. к примеру страницы регистрации, где логика навигации своя, ввод текста, проверка символов, анимация загрузки.


        1. Sazonov
          27.11.2023 15:57

          Qt это не про страницы, это про десктоп ;)

          Вы, скорее всего, неправильно понимаете назначение модели. Модель - это слой (паттерн адаптер) над бизнес логикой который вы пишите для того, чтобы ваша бизнес логика автоматически подхватывалась представлением (view). А события взаимодействия через контроллер уже меняют модель, которая меняет ваши данные.

          Навигация есть. Ввод текста тоже. Валидаторы есть, красивые анимации вообще в пол тычка делаются. Читайте документацию и смотрите код примеров.


  1. garwall
    27.11.2023 15:57
    +7

    Ну хочется уточнить, что вы написали не столько терминал, сколько обертку для qt-библиотеки. Обработки ни одной команды VT100 у вас нет.


    1. DrArgentum Автор
      27.11.2023 15:57

      Это да, грешу этим.


  1. voldemar_d
    27.11.2023 15:57

    -> void

    Зачем это в описании лямбды? У вас какой-то древний компилятор C++?


  1. Sazonov
    27.11.2023 15:57
    -1

    Чем ваш пример настолько сильно отличается от содержимого папки examples в репозитории qtermwidget5 который вы используете, что для этого понадобилась целая статья?


    1. Sazonov
      27.11.2023 15:57

      Минусующие приведут какие-либо аргументы?


  1. gnacad
    27.11.2023 15:57

    Какой дилетантский код, я бы такой на пушечный выстрел не подпустил