#!/usr/bin/env tclsh8.6
#
# xnotify.tcl: show message notifications using xnotify.
#
# To install in the default directory:
#
#	# make install
#
# To install on a different prefix (i.e. your homedir)
#
#	$PREFIX=${HOME} make install
#
# The extension processes PRIVMSG commands, checking if:
#
# - The user is mentioned or
# - The chat the message is from is a private one
# - The application is on focus
#
# If the condition above holds true, a notification popup will appear
# on the screen.
#
# In addition, clicking on the popup will raise IRCTk, switching
# automatically to the channel with the reported message in it.
#
# This extension requires xnotify to be install in order to work.
#

#
# uuid package from Tcllib:
#
# https://core.tcl-lang.org/tcllib
#
package require uuid

set name "XNotify"
set version 1.0.0
set protoversion 1.0

proc newid {} {
	uuid::uuid generate
}

#
# Commands for various message formats as mandated by the IRCTk
# protocol specification:
#
#	https://lab.abiscuola.org/irctk/doc/trunk/www/wiki/extensions.wiki
#

proc handshake {} {
	puts [encoding convertto utf-8 [format "%s\thandshake\t1.0\t%s\t%s\r" \
	    [::newid] $::name $::version]]
}

proc ack {id {msg "ok"}} {
	puts [encoding convertto utf-8 [format "%s\tack\t%s\r" $id $msg]]
}

proc nack {id {msg "ko"}} {
	puts [encoding convertto utf-8 [format "%s\tnack\t%s\r" $id $msg]]
}

proc filter {type} {
	puts [encoding convertto utf-8 [format "%s\tfilter\t%s\r" \
	    [::newid] $type]]
}

proc writemsg {net chan cmd line} {
	puts [encoding convertto utf-8 [format \
	    "\tirc\t\t\t\t\t\t\t%s\t%s\t\t%s\t%s\r" \
	    "$net" "$chan" "$cmd" "$line"]]
}

#
# stdin callback. Used to read messages from IRCTk
#
proc readline {fd} {
	if {[gets stdin line] < 0} {
		if {[eof stdin]} {
			set ::rvalue 0
		}

		return
	}

	#
	# Remember that the messages are exchanged as UTF-8
	#
	set msg [split [encoding convertfrom utf-8 $line] "\t"]

	switch -exact -- [lindex $msg 1] {
		handshake {
			set id [lindex $msg 0]

			#
			# Run the handshake. The protocol is retro-compatible,
			# so we check if the version in our extension is too new
			# for the version of IRCTk we are running.
			#
			if {[lindex $msg 2] < $::protoversion} {
				nack $id "$::name: Incompatible protocol version"

				set ::rvalue 1
			} else {
				ack $id

				handshake

				filter irc
				filter privmsg

				flush stdout
			}
		} irc {
			#
			# If the user is actively using the program, there is no
			# need to show the notification.
			#
			if {"[lindex $msg 7]" eq "away" || "[lindex $msg 6]" eq true} {
				return
			}

			#
			# Show the popup
			#
			if {"[lindex $msg 5]" eq "mention"} {
				puts $fd [format "CMD:%s,%s\t%s on %s: %s" \
				    [lindex $msg 8] [lindex $msg 9] \
				    [lindex $msg 4] \
				    [lindex $msg 9] \
				    [string trimleft [join [lrange $msg 12 end] "\t"] ":"]]

				flush $fd
			}
		}
	}
}

#
# This happen when a user clicks on a notification window
#
proc showwindow {fd} {
	if {[gets $fd line] < 0} {
		puts stderr "can not read from xnotify"

		set ::rvalue 1

		return
	}

	set msg [split $line ","]

	#
	# Tell IRCTk to come up in foreground.
	#
	writemsg [lindex $msg 0] [lindex $msg 1] "RAISE" ""

	#
	# Switch to the channel with the message mentioning us.
	#
	writemsg [lindex $msg 0] [lindex $msg 1] "SWITCH" [lindex $msg 1]
}

#
# That's right. The extension runs it's own instance of xnotify as a child.
#
set fd [open "|xnotify -G NE -s 10 -b 1" r+]

#
# Events.
#
fileevent stdin readable "readline $fd"
fileevent $fd readable "showwindow $fd"

vwait rvalue

exit $rvalue
