SnitXWindow

A Snit Widget which can embed almost any application on X-Window window managers

Yes, I use a tiling window manager - I3, gnu screen for managing terminals. But still I feel do need to better group related windows together. That's what SnitXWindow for. You can embed an arrange all related windows into one Tk window. You can put them in tabs, or paned windows. You can move or activate your Terminals, editor, your pdf viewer your browser window, your spreedsheet etc all related to your project in one step.

This extension works only on X-Windows, requires TkXext and Snit and optionally the Surf webbrowser, Mupdf or Zathura pdf viewers.

Hopefully that X will be alive for some time despite of Wayland ...

Sample run: You can type direct mupdf or zathura in the Window entries and a file dialog will be open. Or you type part of a window name into the entry, the window will be embedded. On the notebook you can use Ctrl-1 to add a new empty frame ready to catch other windows, with Ctrl-2 you add an panedwindow entry below.

WikiDBImage SnitXWindow.png

#  Created By    : Dr. Detlef Groth
#  Created       : Mon Jan 29 05:11:44 2018
#  Last Modified : <180216.0722>
#
#  Description         : A snit widget for embedding other X-applications
#                   
#
#  Notes         : xprop require to get window properties
#                  using the commandline tool xprop
#  Requirements  : xprop , should be installed on X-Window systems
#
#  History       : 0.1 initial release 2018-01-29
#                : 0.2 embedding zathura, mupdf in the empty field
#                      window title using xprop
#                : 0.3 avoid reparenting already embeded window
#        
##############################################################################
#
#  Copyright (c) 2018 Dr. Detlef Groth.
# 
#  License BSD

package require Tk
package require TkXext
package require snit
package require SnitTtkNotebook
package require SnitXSurf
package require SnitXMupdf
package provide SnitXWindow 0.3

snit::widget SnitXWindow {
    option -cmd xterm
    option -embedoption ""
    option -searchtitle ""
    option -cmdargs ""
    variable app ""
    variable pid ""
    variable WinTitle ""
    variable DynTitle ""
    constructor {args} {
        $self configurelist $args
        pack [frame $win.status] -side top -fill x -expand false
        pack [label $win.status.lb -textvariable [myvar DynTitle]] -side left -fill x -expand false
        # install check
        set ok [auto_execok $options(-cmd)]
        if {$options(-cmd) != ""} {
            if {$ok eq ""} {
                return -code error "please install $options(-cmd)"
            }
            $self LoadApp
        } else {
            $self EmbedByTitleFrame
        }
        bind $win <Enter> [mymethod EnterWin]
       
        bind $win <Control-r> [mymethod LoadApp]
        bind $win <Control-o> [mymethod ReopenWithNewFile]   
       
    }
    method EmbedByTitleFrame {} {
        if {![winfo exists $win.top]} {
            frame $win.top
            pack [label $win.top.lbl -text "Window Title:"] -padx 3 -side left
            pack [entry $win.top.ent -textvariable [myvar WinTitle]] -side left -padx 3
            pack [button $win.top.btn -text "Embed Window" -command [mymethod EmbedTitle]] -side left -padx 3
            pack $win.top -fill x -expand false -side top
        }
    }
    method ReadPipe {chan} {
        set d [read $chan]
        foreach line [split $d \n] {
            if {[regexp {^WM_NAME.STRING. = "(.+)"} $line m title] } {
                set DynTitle $title
            } 
        }
        if {[eof $chan]} {
            fileevent $chan readable {}
            close $chan
        }
    }
    method getWinState {} {
        set state Normal
        set res [exec xprop -id 0x$app]
        foreach line [split $res "\n"] {
            if {[regexp {window state: ([^ ]+)} $line -> state]} {
                break
            }
        }
        return $state
    }
    method getWinProperties {} {
        set xpin [open "|xprop -id 0x$app -spy" r]
        fconfigure $xpin -blocking 0 -buffering line
        fileevent $xpin readable [mymethod ReadPipe $xpin]
    }
    method EmbedTitle {} {
        if {[regexp {surf +(.+)} $WinTitle -> url]} {
            SnitXSurf $win.f -home $url

        } elseif {[regexp {zathura} $WinTitle] || [regexp {mupdf} $WinTitle] } {
            set types {
                {{Pdf Files}       {.pdf}        }
                {{Ps  Files}       {.ps}         }                
                {{Comic  Files}    {.cbz}        }                
                {{All Files}       { *}          }
            }
            if {[regexp {mupdf} $WinTitle] } {
                set types {
                    {{Pdf Files}       {.pdf}    }
                    {{Ebook Files}     {.epub}   }                    
                    {{Comic  Files}    {.cbz}    }                
                    {{All Files}       { *}     }
                }
            }
            set filename [tk_getOpenFile -filetypes $types]
            if {$filename != ""} {
                if {[regexp {zathura} $WinTitle]} {
                    SnitXWindow $win.f -cmd zathura -cmdargs $filename -searchtitle $filename
                } else {
                    SnitXMupdf $win.f -infile $filename -standalone false
                }
            }
        } else {
            set app [TkXext.find.window $WinTitle];
            if {![winfo exists $win.f]} {
                frame $win.f -width 200 -height 200 -container 1
            }
            while {$app eq ""} {
                after 200
                set app [TkXext.find.window $WinTitle];
                if {[incr x] > 10} {
                    break
                }
            }
            if {$app eq "" || $app == 0} {
                tk_messageBox -title "Info!" -icon info -message "Unable to find window '$WinTitle'!\nHint: Try a * at the End!" -type ok
                return
            }
            TkXext.reparent.window $app [winfo id $win.f]
            bind $win.f <Configure> [list TkXext.resize.window $app %w %h]
            bind $win.f <Destroy> [list TkXext.delete.or.kill $app]
            bind $win <Control-r> [list pack $win.top]
            $self getWinProperties
        }
        pack $win.f -fill both -expand 1 -side top
        pack forget $win.top
    }
              
    method ReopenWithNewFile {} {
        if {$options(-cmd) eq "mupdf"} {
            set types {
                {{Pdf Files}       {.pdf}        }
                {{Epub Files}      {.epub}        }
                {{All Files}        *             }

            }
            set filename [tk_getOpenFile -filetypes $types]
            if {$filename != ""} {
                puts $filename
                set options(-cmdargs) [file nativename $filename]
                set options(-searchtitle) "[file tail [file nativename $filename]]*"
                if {[winfo exists $win.f]} {
                    destroy $win.f
                }
                $self LoadApp
            }
        }
    }
    method LoadApp {} {
        if {![winfo exists $win.f]} {
            frame $win.f -width 200 -height 200 -container 1
        }
        
        if {$options(-embedoption) eq ""} {
            set pid [exec $options(-cmd) {*}$options(-cmdargs) &]
        }
        set app ""
        while {$app eq ""} {
            after 200
            set app [TkXext.find.window $options(-searchtitle)];
            set state [$self getWinState]
            if { $state eq "Withdrawn" } {
                # window was already catched
                # wait for the fresh one!!
                # don't steal
                set app ""
            }
            if {[incr x] > 100} {
                break
            }
        }
        if {$app eq ""} {
            destroy $win.f
            return -code error "unable to find the $options(-cmd) window"
        }
        TkXext.reparent.window $app [winfo id $win.f]
        bind $win.f <Configure> [list TkXext.resize.window $app %w %h]
        pack $win.f -fill both -expand 1 
        if {$options(-cmd) eq "urxvt"} {
            pack forget $win.status
        } else {
            $self getWinProperties
        }
    }
    method EnterWin {} {
        catch {focus -force $win}
    }
    destructor {
        try {
            exec kill $pid
        } finally {
            try {
                # TkXext.delete.or.kill $app
                # did not work
            }
        }
        destroy $win
    }
}

if {$argv0 eq [info script]} {
    if {[llength $argv] <= 1 } {
        # simple window manager
        # just create a simple workspace
        # ready to add addtional windows
        set pw1 [panedwindow .pw1 -orient horizontal -width 100 -height 100]
        set nb [SnitTtkNotebook .pw1.nb]
        set pw2 [panedwindow $nb.pw2 -orient vertical -width 100 -height 100]
        SnitXWindow $pw2.term -cmd xterm -cmdargs "-title myurxvt -rv" -searchtitle myurxvt        
        SnitXWindow $pw2.empty -cmd ""        
        SnitXWindow $pw1.editor -cmd "" 
        #
        SnitXSurf $nb.surf -home http://www.duckduckgo.com
        SnitXWindow $nb.empty -cmd ""
        $nb add $pw2 -text "Xterm/App" 
        $nb add $nb.surf -text "Surf"         
        $nb add $nb.empty -text "Empty" 
        
        $pw1 add $nb
        $pw1 add $pw1.editor
        
        $pw2 add $pw2.term
        $pw2 add $pw2.empty
        pack $pw1 -side top -fill both -expand true
        # example mupdf: SnitXWindow $pw2.mupdf -cmd mupdf -cmdargs "/path/to/Berg-2006-BMCGenomics-PCA.pdf" -searchtitle "Berg-2006-BMCGenomics-PCA.pdf*"
        # example editor: SnitXWindow $pw1.me -cmd $me -cmdargs "-cdefault test.c " -searchtitle "MicroEmacs: default: test.c"
        # example urxvt: SnitXWindow $nb.urxvt -cmd urxvt -cmdargs "-title myurxvt -rv" -searchtitle myurxvt        
        # example gnumeric: SnitXWindow $nb.gnum -cmd gnumeric -cmdargs "/path/to/file.xls" -searchtitle "*file.xls*"
        proc addTab {} {
            global nb
            set x [llength [$nb tabs]]
            SnitXWindow $nb.empty$x -cmd ""
            $nb add $nb.empty$x -text "Tab $x" 
        }
        proc addSplitTab {} {
            global nb
            set x [llength [$nb tabs]]
            set pw [panedwindow $nb.empty$x -orient vertical]
            set x1 [SnitXWindow $nb.empty$x.x1 -cmd ""]
            set x2 [SnitXWindow $nb.empty$x.x2 -cmd ""]
            $nb add $nb.empty$x -text "Tab $x" 
            $pw add $x1 $x2
        }
        $nb bind <Control-t> "" ;# remove binding for new tab
        $nb bind <Control-KeyPress-1> addTab
        $nb bind <Control-KeyPress-2> addSplitTab
    }
}