Оглавление

Нотификатор

Возможности (философия)

Я очень долго пытался найти подходящий нотификатор и в конце концов сделал то, что меня полностью удовлетворяет.

Код

#!/usr/bin/wish

# :action:color:bgcolor:timeout:message

## Setup ###########################################################

set GEOMETRY -100-0
set FONT     {-cronyx-helvetica-bold-r-*-*-14-*-*-*-*-*-koi8-r}
set PORT     7778

# /Setup ###########################################################

###### EVENTS

set messages {}

proc shift_message {} {
    global messages # not changed directly
    remove_message [lindex $messages end]
}

proc drop_all_messages {} {
    global messages # not changed directly
    foreach x $messages {
        remove_message $x
    }
}

###### DISPLAING

proc update_label {} {
    global messages # not changed directly
    set m [lindex $messages end]
    set l [llength $messages]
    if {$l == 0} {
        wm withdraw .
    } else {
        if {$l == 1} {
            set a {}
        } else {
            set a " ($l)"
        }
        .l configure -text "[string range $m 14 end]$a" \
                     -fg "#[string range $m 0 5]" \
                     -bg "#[string range $m 7 12]"
        wm deiconify .
        raise .
    }
}

###### TIMERS

array set message_timeouts {}

proc set_timeout_for_message {mess timeout} {
    global message_timeouts
    unset_timeout_for_message $mess
    if {$timeout > 0} {
        set t [after [expr {$timeout * 1000}] [list remove_message $mess]]
        set message_timeouts($mess) $t
    }
}

proc unset_timeout_for_message {mess} {
    global message_timeouts
    set t [array names message_timeouts -exact $mess]
    if {[llength $t] == 1} {
        after cancel $message_timeouts($mess)
        unset message_timeouts($mess)
    }
}

###### PROCESSING

proc remove_message {mess} {
    global messages
    set i [lsearch -exact $messages $mess]
    if {$i >= 0} {
        set messages [lreplace $messages $i $i]
        update_label
        unset_timeout_for_message $mess
    }
}

proc insert_message {mess timeout} {
    global messages
    lappend messages $mess
    update_label
    set_timeout_for_message $mess $timeout
}

proc append_message {m} {
    # -- parse
    if {[regexp {(?x)^
            :(set|clean|)
            :([0-9a-fA-F]{6}|)
            :([0-9a-fA-F]{6}|)
            :(\d*)
            :(.*)$
        } $m x action color bgcolor timeout text]} {
    } else {
        set action {set}
        set color {}
        set bgcolor {}
        set timeout {}
        set text $m
    }
    if {$color == {}} {set color {000000}}
    if {$bgcolor == {}} {set bgcolor {999999}}
    if {$timeout == {}} {set timeout -1}
    # -- process
    set mess "$color:$bgcolor:$text"
    remove_message $mess
    if {$action != {clean}} {
        insert_message $mess $timeout
    }
}

###### NETWORK

array set accumulators {}
array set timeouts {}

proc read_from_channel {channel} {
    global accumulators
    set s [read $channel]
    if {$s == {}} {
        close_channel $channel
    } else {
        set t "$accumulators($channel)$s"
        set i [string first "\n" $t]
        if {$i > 0} {
            set accumulators($channel) [string range $t 0 [expr {$i - 1}]]
            close_channel $channel
        } else {
            set accumulators($channel) $t
        }
    }
}

proc close_channel {channel} {
    global accumulators timeouts
    append_message $accumulators($channel)
    after cancel $timeouts($channel)
    unset accumulators($channel)
    unset timeouts($channel)
    fileevent $channel readable {}
    close $channel
}

proc setup_channel {channel} {
    global accumulators timeouts
    #### DEBUG
    puts {-- on open}
    puts "acc = [array get accumulators]"
    puts "tos = [array get timeouts]"
    global messages message_timeouts
    puts "mes = $messages"
    puts "mto = [array get message_timeouts]"
    puts {--}
    ####
    fconfigure $channel -blocking 0
    set timeout [after 2000 [list close_channel $channel]]
    set timeouts($channel) $timeout
    set accumulators($channel) {}
    fileevent $channel readable [list read_from_channel $channel]
}

proc accept_connection {channel addr port} {
    setup_channel $channel
}

###### MAIN

socket -server accept_connection $PORT

wm title . {uNote}
tk appname {uNote}
wm overrideredirect . 1

label .l -font $FONT -padx 10 -pady 1
pack .l

#tkwait visibility .
wm geometry . $GEOMETRY

bind . <1> shift_message
bind . <3> drop_all_messages

append_message {:set:000000:ffffcc:10:uNote (c) Michurin 2010-2011}

Формат сообщения

Простые сообщения

Если сообщение не поддаётся разбору, то оно считается простым и выводится дефолтными цветами и без таймаута.

Форматированные сообщения

:action:foreground:background:timeout:message
  ^      ^          ^          ^       ^
  |      |          |          |       |
  |      |          |          |       `-- текст сообщения
  |      |          |          `-- таймаут в секундах, по прошествии
  |      |          |              которого сообщение будет автоматически
  |      |          |              забыто
  |      |          `-- цвета в HTML-формате без префикса '#'
  |      `------------- то есть ffffff — белый, ff0000 — красный
  `-- действие, поддерживаются set и clean

Примеры

Просто вывести 'OK!'.

echo 'OK!' | nc localhost 7778

Вывести 'OK!' белого цвета на зелёном фоне на десять секунд.

echo '::ffffff:009900:10:OK!' | nc localhost 7778

Запланировать вывод сообщения на 18:30.

echo 'echo ":set:990000:ffff00::It is 18:30 now!" | nc localhost 7778' | at 18:30

Примеры использования в скриптах

Ожидание завершения работы процесса

#!/bin/sh

pid=$1
while test `ps h $pid | wc -l` -eq 1
do
    echo -en "\e[1G\t"
    echo -n `date`
    echo -en "\e[K\e[1G"
    sleep 3
done
echo ''
echo ":set:990000:ffff00::PID=$pid DONE" | nc localhost 7778

Выдаёт сообщение о том, что завершился процесс с заданным PID.

Ожидание завершения формирования файла

#!/bin/sh

fl="$1"
os=''
while
    if test -e "$fl"
    then
        s=`stat -c %Y "$fl"`
        test "${os:-0}" -ne "$s"
    else
        false
    fi
do
    echo -en "\e[1G\t"
    echo -n "$os -> $s"
    echo -en "\e[K\e[1G"
    os="$s"
    sleep 120
done
echo ''
echo "FILE $fl DONE" | nc localhost 7778

Выдаёт сообщение, когда файл перестал изменяться (закончено скачивание, конвертирование или ещё что-то).

Internet-радио

#!/bin/sh

#url='http://scfire-ntc-aa02.stream.aol.com:80/stream/1049'
url='http://scfire-ntc-aa03.stream.aol.com:80/stream/1049'
#url='http://scfire-dtc-aa03.stream.aol.com:80/stream/1049'
#url='http://scfire-dtc-aa01.stream.aol.com:80/stream/1049'
#url='http://scfire-ntc-aa04.stream.aol.com:80/stream/1049'
#url='http://scfire-dtc-aa05.stream.aol.com:80/stream/1049'
#url='http://scfire-dtc-aa06.stream.aol.com:80/stream/1049'

mplayer -quiet "$url" </dev/null |
while read l
do
    echo "| $l"
    if echo $l | grep 'ICY Info' 2>&1 >/dev/null
    then
        u=`echo "$l" | sed 's/.*StreamUrl=.//;s/..$//;'`
#        echo "u=[$u]"
        if `echo "$u" | grep covers 2>&1 >/dev/null`
        then
          f=/tmp/radio/${u##*/}
#          echo "test file [$f]"
          if test ! -e "$f"
          then
#            echo "wget file [$f]"
#            echo wget -qO "$f" "$u"
            mkdir -p /tmp/radio
            wget -qO "$f" "$u"
          fi
          (
          echo "display file [$f]"
          display -geometry +0-0 "$f" &
          sleep 15
          kill %-
          ) &
        else
           echo "[not cover]"
        fi
        l=`echo "$l" | sed 's/ICY Info: StreamTitle=.//;s/.;StreamUrl=.*//'`
#        l=`echo "$l" | sed 's/ICY Info: StreamTitle=.//;s/-.*//'`
        echo ":set:aaccaa:223322:30:$l" | nc localhost 7778
    fi
done

Скрипт проигрывает интернет-радио, показывает обложки альбомов (это фича Radio Paradise; ваша любимая радиостанция может и не передавать обложки альбомов; нужен ImageMagic) и названия композиций.