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.
# 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 } }