#!/bin/env wish set confdir ~/.config/ntalk set scriptpath "${confdir}/cscript.tcl" set sixelpath "${confdir}/sixels.txt" file mkdir $confdir proc quit {} {exit 0} proc restart {} { global argv0 exec [info nameofexecutable] $argv0 & exit 0 } wm title . ". o ( ntalk ) o ." tk appname ntalk ### SIXEL PARSING LIB ### set images {} proc char2n {char} { binary scan $char c n expr {($n&0x7F)-0x3F} } proc chars2bytes {sixels} { set ret {} set nums {} foreach sixel $sixels { lappend nums [char2n $sixel] } for {set i 0} {$i < 6} {incr i} { set s "" foreach n $nums { append s [expr {($n>>$i)&1}] } append s [string repeat "0" [expr {7 - (([string length $s] + 7) % 8)}]] binary scan [binary format b* $s] c* bytes foreach byte $bytes { lappend ret [format "%02x" [expr {$byte&0xff}]] } } return $ret } proc splitsixels {str} { set ret {} set row {} foreach c [split $str {}] { switch -regexp -- $c { - { lappend ret $row set row {} } [?-~] { lappend row $c } default { # ignored } } } if {$row != {}} { lappend ret $row } return $ret } proc sixels2xbm {sixels} { set rows [splitsixels $sixels] set ba {} if {[llength $rows] == 0} { return {} } # if {[llength $rows] > 3} { set rows [lrange $rows 0 2] } set height [expr {min(48, [llength $rows]*6)}] set width 0 foreach row $rows { set width [expr {max($width, [llength $row])}] } if {$width == 0} { return {} } foreach row $rows { append row [string repeat " ?" [expr {$width - [llength $row]}]] lappend bytes {*}[chars2bytes $row] } set nbytes [expr {int(ceil($width / 8.0)) * $height}] set bytes [lrange $bytes 0 $nbytes-1] set ret "#define img_width $width\n#define img_height $height\n" append ret "static unsigned char img_bits\[\] = {\n\t" foreach byte $bytes { append ret "0x" $byte ", " } set ret [string range $ret 0 end-2] append ret "\n\t};\n" return $ret } proc sixels2image {sixels} { global images if {[dict exists $images "sixel:$sixels"]} { return [dict get $images "sixel:$sixels"] } set xbm [sixels2xbm $sixels] if {[dict exists $images "xbm:$xbm"]} { return [dict get $images "xbm:$xbm"] } set image [image create bitmap -data $xbm] dict set images "sixel:$sixels" $image dict set images "xbm:$xbm" $image dict set images "image:$image" $sixels return $image } ### FONT STUFF ### font create testingFont -size 100 proc make16 {} { foreach font {TkDefaultFont TkFixedFont TkTextFont} { font configure testingFont -family [font configure $font -family] set fontsize [expr {int(ceil(16.0 / [font metrics testingFont -linespace] * 100))}] font configure $font -size $fontsize } } make16 ### UI SETUP ### frame .foot entry .foot.input label .foot.msgs -textvariable lastmsg label .foot.sep -text " // " label .foot.ppl -textvariable clients label .foot.name scrollbar .scroll -command {.buffer yview} text .buffer -height 24 -width 128 -yscrollcommand {.scroll set} pack .foot.ppl .foot.sep .foot.msgs -side right pack .foot.name -side left pack .foot.input -side bottom -fill x pack .foot -side bottom -fill x pack .scroll -side right -fill y pack .buffer -fill both if {[catch {package require history}] == 0} { history::init .foot.input bind .foot.input { if {[.foot.input get] != {}} { history::add .foot.input [.foot.input get] } } } bind . quit bind . restart bind . {.menu.opt invoke "show raw sixel codes"} .buffer tag configure rawsixel -elide true -foreground DarkSlateGrey ### MENU ### menu .menu menu .menu.nt -tearoff 0 menu .menu.opt -tearoff 1 menu .menu.sixels -tearoff 1 -title "sixel picker" menu .menu.sixels.rm -tearoff 0 .menu add cascade -label "ntalk" -menu .menu.nt .menu.nt add command -label "about ntalk" -command { tk_dialog .about "about ntalk" \ "ntalk\nby aleteoryx\nlast updated 2025-09-20" \ "info" \ 0 okay } .menu.nt add separator .menu.nt add command -label "restart" -command restart -accelerator "Ctrl-Shift-R" .menu.nt add command -label "quit" -command quit -accelerator "Ctrl-Q" .menu add cascade -label "sixels" -menu .menu.sixels .menu.sixels add separator .menu.sixels add cascade -label "delete a sixel..." -menu .menu.sixels.rm .menu add cascade -label "options" -menu .menu.opt .menu.opt add checkbutton -label "show raw sixel codes" \ -accelerator "Ctrl-s" -variable showsixel -command { .buffer tag configure rawsixel -elide [expr {!$showsixel}] } . configure -menu .menu ### USER SIXEL LIBRARY ### set sixellib {} proc savesixels {} { global sixellib sixelpath set fp [open $sixelpath w] foreach line [lreverse $sixellib] { if {$line == {}} { puts $fp "" continue } lassign $line name data puts $fp "$name = $data" } close $fp } proc rmsixel {n} { global sixellib set sixellib [lreplace $sixellib $n $n] savesixels regensixelmenu } proc getsubmenu {menu name} { while {[set idx [string first / $name]] != -1} { set chunk [string trim [string range $name 0 $idx-1]] set name [string trim [string range $name $idx+1 end]] set submenu "${menu}.u$chunk" catch { menu $submenu -tearoff 0 $menu insert 0 cascade -menu $submenu -label $chunk } set menu $submenu } return [list $menu $name] } proc regensixelmenu {} { global sixellib .menu.sixels delete 0 [expr {[.menu.sixels index end]-2}] .menu.sixels.rm delete 0 end foreach menu [winfo children .menu.sixels] { if {$menu == ".menu.sixels.rm"} continue destroy $menu } destroy {*}[winfo children .menu.sixels.rm] for {set i 0} {$i < [llength $sixellib]} {incr i} { if {[lindex $sixellib $i] == {}} continue lassign [lindex $sixellib $i] name data set data [list "\\($data)"] lassign [getsubmenu .menu.sixels $name] menu label $menu insert 0 command -label $label -command [subst { .foot.input insert insert $data }] lassign [getsubmenu .menu.sixels.rm $name] menu label $menu insert 0 command -label $label -command [subst { rmsixel $i }] } } if {[file readable $sixelpath]} { set fp [open $sixelpath] while {![eof $fp]} { gets $fp line if {$line == {}} { lappend sixellib {} continue } set idx [string first "=" $line] set name [string trim [string range $line 0 $idx-1]] set data [string trim [string range $line $idx+1 end]] lappend sixellib [list $name $data] } set sixellib [lreverse $sixellib] close $fp } else { set sixellib {} } regensixelmenu set clickedimage {} proc finishsixel {} { global clickedimage images sixellib set data [dict get $images "image:$clickedimage"] regsub "=" [.namesixel.entry get] ":" name lappend sixellib [list $name $data] savesixels regensixelmenu destroy .namesixel } menu .savesixel -tearoff 0 .savesixel add command -label "save sixel..." -command { bind .