Cから

Cから

私はベルを押す方法を知っていますecho -ne '\a'(または外部コマンドに頼ることが気に入らない場合はもっと良いですtput bel:)。しかし、実際に行うことは、出力に特別なコードを送信することです。対応する出力が端末の場合、端末はコードと着信音を解釈します。

私の問題は、どの端末でも出力を読み取ることができないバックグラウンドスクリプトで警告音を鳴らしたいということです。ハーモニーファイルのような.wavものpaplayを再生することもできますが、シンプルでラフなビープ音が好きです。外部プログラムに頼りたくありません。

したがって、質問は次のようになります。

  1. ターミナルからの着信音は、どのような意味で特別なもの(特別なハードウェアを使用)、または他のものと同じサウンドファイルですか?
  2. その音をどのように再現しますか?

答え1

プログラムソースコードを勉強した後beep、アラームを鳴らす最小限のCプログラムを思い出しました。

これは、Linuxカーネルで通常名前付きevdevデバイス/dev/input/by-path/platform-pcspkr-event-spkr(実際の場所へのシンボリックリンク)として提供されるPCスピーカーを使用します。

スピーカーの電源を入れるイベントがあります(つまり与えられた周波数で音を出し始める)とスピーカーをオフにする別のイベント(つまり音を止めてください)。最初のイベントを送信し、必要な期間スリープモードに切り替えてから2番目のイベントを送信します。

これを行うには、udevルール、プログラムのsetuidビットを介して設定することができますbeep。とにかく、xsetプログラム(Xサーバーファミリ)とターミナルエミュレータにはこれらの制限はありません。

Cから

#include <linux/input.h>  // struct input_event
#include <fcntl.h>  // open
#include <unistd.h>  // write, close
#include <time.h>  // nanosleep

/* frequency (Hz): */
static unsigned int const default_frequency = 440;
/* duration (ms): */
static unsigned int const default_duration = 200;
/* file path of PC speaker: */
static char const * const default_device = "/dev/input/by-path/platform-pcspkr-event-spkr";

void start_beep(int fd, int freq)
{
    struct input_event e = { 0 };
    e.type = EV_SND;
    e.code = SND_TONE;
    e.value = freq;
    write(fd, &e, sizeof(e));
}

void stop_beep(int fd)
{
    start_beep(fd, 0);
}

void sleep_ms(unsigned int ms)
{
    struct timespec duration =
        { .tv_sec  = ms / 1000U,
          .tv_nsec = (long)(ms % 1000UL * 1000UL * 1000UL) };
    nanosleep(&duration, NULL);
}

int main(void)
{
    int fd = open(default_device, O_WRONLY);
    start_beep(fd, default_frequency);
    sleep_ms(default_duration);
    stop_beep(fd);
    close(fd);
    return 0;
}

(簡潔さと明確さのために、上記のソースコードはすべてのセキュリティチェックをスキップします。(ファイルがあることを確認し、スキャンが成功したことを確認し、ファイルopen記述子が文字デバイスであることを確認し、頻度と期間が整数であることを確認します)。許容範囲を確認するには、成功かどうかwrite... nanosleep)実際のアプリケーションではこの問題を解決する必要があります。

シェルスクリプトから

上記のCプログラムを使用してイベントの形式を観察し、次にシェルスクリプトで同じイベントを作成できます。残念ながら、バイナリ形式はプラットフォームによって異なる可能性があります。 Linuxカーネル5.2がインストールされているx86-64システムでは、サウンドイベントの構造は次のとおりです。

  • 16バイト:0
  • 2バイト、リトルエンディアン:0x12(type値を持つフィールドEV_SND
  • 2バイト、リトルエンディアン:0x2(code値を持つフィールドSND_TONE
  • 4バイト、リトルエンディアン:周波数(Hz)または0(サウンド停止)(フィールドvalue

このバイナリ構造は Perl 関数を使って書くことができます。pack。バイナリ整数も出力可能純粋なバッシュを使うしかし、これはより詳細に説明されています。

# frequency (Hz):
default_frequency=440
# duration (ms):
default_duration=200
# file path of PC speaker:
default_device='/dev/input/by-path/platform-pcspkr-event-spkr'

function start_beep()
{
    declare -i freq="$1"
    perl -e 'print pack("qqssl", 0, 0, 0x12, 2, '$freq')'
}

function stop_beep()
{
    start_beep 0
}

# USAGE: beep [FREQUENCY [DURATION [DEVICE]]]
function beep()
{
    declare -i freq="${1-$default_frequency}"
    declare -i dur="${2-$default_duration}"
    declare dev="${3-$default_device}"
    # convert milliseconds to seconds
    declare dur_sec=$( printf '%u.%03u' $(( dur / 1000 )) $(( dur % 1000 )) )
    # write the sound events
    {
        start_beep $freq
        sleep $dur_sec
        stop_beep
    } >> "$dev"
}

答え2

スクリプトがrootとして実行されている場合は、単に次のことができます。

echo -ne '\a' >/dev/console

関連情報