Эта статья поможет сэкономить время тем, кто сталкивается с подобными ситуациями - когда нужно перенести некоторые количества текста в такие места, куда он простым буфером обмена не переносится, например - в remote console виртуального или физического сервера, как Proxmox или iBMC / iLO.

Аналогичное решение "ClickPaste" (с гитхаба) показалось мне чуть менее удобным - захотелось доработать идею и сделать ввод более функциональным и наглядным.

Особенности моей реализации:
• запуск из одиночного исполняемого файла (portable) или из легковесного архива;
• многострочный ввод (симулирует нажатие клавиши enter между строк);
• редактирование и превью в окне ввода;
• режим медленного ввода;
• русская локализация

При нажатии на кнопку "Type for me!", программа переключится на предыдущее активное окно и симулирует либо быстрый ввод, либо с задержками между символами при отмеченном чекбоксе "Slow mode". Закрытие окна программы немедленно прекращает её выполнение.

Исходный код (C#)
using System.Text.RegularExpressions;

namespace typewriter
{
    public partial class typewriter : Form
    {
        public typewriter()
        {
            InitializeComponent();
        }

        // Обработчик события для закрытия формы
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            base.OnFormClosing(e);

            // Останавливаем выполнение программы при закрытии окна
            Environment.Exit(0);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            string inputText = textBox1.Text;

            // Переключаемся на предыдущее активное окно (Alt + Tab)
            SendKeys.SendWait("%{TAB}");

            // Задержка на время переключения окна
            Thread.Sleep(1500); // полторы секунды

            // Обработка текста
            foreach (char c in inputText)
            {

                if (c == '\n') // Если символ - перенос строки
                {
                    // Ничего не делаем, чтобы заблокировать лишние переносы строки
                }
                else
                {
                    if (checkBox1.Checked) // Медленный режим
                    {
                        // Экранируем проблемные знаки
                        string txt = Regex.Replace(c.ToString(), "[+^%~(){}]", "{$0}");
                        SendKeys.SendWait(txt);
                        Thread.Sleep(100); // 100 миллисекунд между символами, если чекбокс включен
                    }
                    else
                    {
                        string txt = Regex.Replace(c.ToString(), "[+^%~(){}]", "{$0}");
                        SendKeys.SendWait(txt);
                    }
                }
            }
            if (checkBox2.Checked) // Заканчивать клавишей "Ввод"
            {
                SendKeys.SendWait("{ENTER}");
            }
        }
        private void checkBox3_CheckedChanged(object sender, EventArgs e)
        {
            this.TopMost = checkBox3.Checked; //Поверх всех окон
        }

        private void button2_Click(object sender, EventArgs e)
        {
            textBox1.Clear(); // Очистить
        }
    }
}

Исходный код (С++)
mainwindow.h
// MAINWINDOW.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui {
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();
    void clearTextEdit();
    void on_action1_triggered();

private:
    Ui::MainWindow *ui;

};

#endif // MAINWINDOW_H

main.cpp

//MAIN.CPP

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
        return a.exec();
}

mainwindow.cpp
//MAINWINDOW.CPP

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QProcess>
#include <QThread>
#include <QCoreApplication>
#include <QKeyEvent>


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    this->setAttribute(Qt::WA_TranslucentBackground, true); // включаем прозрачность
    this->setStyleSheet("background: rgba(255, 255, 255, 0.72);"); // настраиваем прозрачность

    connect(ui->action1, &QAction::triggered, this, &MainWindow::on_action1_triggered);
    connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::on_pushButton_clicked);
    connect(ui->pushButton_2, &QPushButton::clicked, this, &MainWindow::clearTextEdit); // Добавляем обработчик для кнопки pushButton_2

    ui->textEdit->setLineWrapMode(QTextEdit::NoWrap); // отключаем перенос строк

    this->setFixedSize(this->size());
    this->setWindowFlags(this->windowFlags() & ~Qt::WindowMaximizeButtonHint);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked() // Type for me! button
    {
    QProcess process;
    process.start("xdotool", QStringList() << "key" << "Alt+Tab");
    process.waitForFinished();
    QThread::msleep(1500);

        QString inputText = ui->textEdit->toPlainText();
        QStringList lines = inputText.split('\n');

        if (ui->checkBox->isChecked())
        {
            process.start("xdotool", QStringList() << "type" << "--delay" << "333" << lines.first());
            process.waitForFinished();
            process.start("xdotool", QStringList() << "key" << "Return");
            process.waitForFinished();
        }
        else
        {
            process.start("xdotool", QStringList() << "type" << lines.first());
            process.waitForFinished();
            process.start("xdotool", QStringList() << "key" << "Return");
            process.waitForFinished();
        }

        for (int i = 1; i < lines.size(); i++)
        {
            if (ui->checkBox->isChecked())
            {
                process.start("xdotool", QStringList() << "type" << "--delay" << "333" << lines[i]);
                process.waitForFinished();
                process.start("xdotool", QStringList() << "key" << "Return");
                process.waitForFinished();
            }
            else
            {
                process.start("xdotool", QStringList() << "type" << lines[i]);
                process.waitForFinished();
                process.start("xdotool", QStringList() << "key" << "Return");
                process.waitForFinished();
            }
          }
   }

void MainWindow::clearTextEdit() // Clear button
{
    ui->textEdit->clear();
}
void MainWindow::on_action1_triggered() // Drop-down menu button
{
    QProcess::startDetached("gnome-terminal", QStringList() << "--" << "bash" << "-c" << "echo 'sudo apt-get install xdotool'; sudo apt-get install xdotool; exec bash");
    disconnect(ui->action1, &QAction::triggered, this, &MainWindow::on_action1_triggered);
}

Ссылки на скачивание

Microsoft Windows
.exe file, 69MiB
English

typewriter.exe

SHA256 hash:

95E87C4BED4B9BA3FDC339D67AD9E4527C403F3102EFEBDF1C5EAAE170723464
Хеш-суммы в два клика

Русский

машинистка.exe

SHA256 hash:

FE5BC28F43F2ABFAF14BC99627422F167FF0B40C2624CD4E0C18D5CEBFBE18AF
Хеш-суммы в два клика

7Zip archive, 311KB
Русский

typewriter-ru.zip

SHA256 hash:

FE5BC28F43F2ABFAF14BC99627422F167FF0B40C2624CD4E0C18D5CEBFBE18AF
Хеш-суммы в два клика

English

typewriter.zip

SHA256 hash:

908A888A5EDC8EC1D0BEAF415D7EFCCBB2DC17AD7343D1CCD274984A6072B3E3
Хеш-суммы в два клика

Linux
Ubuntu
Русский

typewriter-linux-ru.zip

SHA256 hash:

697D2A05DE35BBEB0B28726ECDC7BAD38EF25D1A6F020B122319DF9750992A53
Хеш-суммы в два клика

English

typewriter-linux.zip

SHA256 hash:

D77F8272D30A4B208E6AECFB353FF6E5AFA0B999D228D98B73ED644EB605DCD5
Хеш-суммы в два клика

edit 18.07:

Исправил найденные баги, перекомпилировал и обновил ссылки; добавил версии для Linux Ubuntu.

Всем спасибо за обратную связь! Ссылка на проект в GitVerse: https://gitverse.ru/aremys/typewriter