#!/bin/sh # This line continues for Tcl, but is a single line for 'sh' \ exec wish "$0" -- ${1+"$@"} # For information on usage and redistribution, and for a DISCLAIMER OF ALL # WARRANTIES, see the file, "LICENSE.txt," in this distribution. # Copyright (c) 1997-2009 Miller Puckette. # "." automatically gets a window, we don't want it. Withdraw it before doing # anything else, so that we don't get the automatic window flashing for a # second while pd loads. if { [catch {wm withdraw .} fid] } { exit 2 } # This is mainly for OSX as older versions only # have 8.4 while newer versions have 8.5. if { [catch {package require Tcl 8.5 9}] } { # Tcl 8.5 not available package require Tcl 8.4 package require Tk } else { # Tcl >=8.5,>=9 is available package require Tk # replace Tk widgets with Ttk widgets on 8.5 namespace import -force ttk::* } package require msgcat # TODO create a constructor in each package to create things at startup, that # way they can be easily be modified by startup scripts # TODO create alt-Enter/Cmd-I binding to bring up Properties panels # Pd's packages are stored in the same directory as the main script (pd-gui.tcl) set auto_path [linsert $auto_path 0 [file dirname [info script]]] package require pd_connect package require pd_menus package require pd_bindings package require pdwindow package require dialog_array package require dialog_audio package require dialog_canvas package require dialog_data package require dialog_font package require dialog_gatom package require dialog_iemgui package require dialog_message package require dialog_midi package require dialog_path package require dialog_startup package require dialog_preferences package require helpbrowser package require pd_menucommands package require opt_parser package require pdtk_canvas package require pdtk_text package require pdtk_textwindow package require pd_guiprefs package require pd_i18n # TODO eliminate this kludge: package require wheredoesthisgo #------------------------------------------------------------------------------# # import functions into the global namespace # gui preferences namespace import ::pd_guiprefs::init namespace import ::pd_guiprefs::update_recentfiles namespace import ::pd_guiprefs::write_recentfiles # make global since they are used throughout namespace import ::pd_menucommands::* # import into the global namespace for backwards compatibility namespace import ::pd_connect::pdsend namespace import ::pdwindow::pdtk_post namespace import ::pdwindow::pdtk_pd_dio namespace import ::pdwindow::pdtk_pd_audio namespace import ::pdwindow::pdtk_pd_dsp namespace import ::pdwindow::pdtk_pd_meters namespace import ::pdtk_canvas::pdtk_canvas_popup namespace import ::pdtk_canvas::pdtk_canvas_editmode namespace import ::pdtk_canvas::pdtk_canvas_getscroll namespace import ::pdtk_canvas::pdtk_canvas_setparents namespace import ::pdtk_canvas::pdtk_canvas_reflecttitle namespace import ::pdtk_canvas::pdtk_canvas_menuclose namespace import ::dialog_array::pdtk_array_dialog namespace import ::dialog_audio::pdtk_audio_dialog namespace import ::dialog_canvas::pdtk_canvas_dialog namespace import ::dialog_data::pdtk_data_dialog namespace import ::dialog_find::pdtk_showfindresult namespace import ::dialog_font::pdtk_canvas_dofont namespace import ::dialog_gatom::pdtk_gatom_dialog namespace import ::dialog_iemgui::pdtk_iemgui_dialog namespace import ::dialog_midi::pdtk_midi_dialog namespace import ::dialog_midi::pdtk_alsa_midi_dialog namespace import ::dialog_path::pdtk_path_dialog namespace import ::dialog_startup::pdtk_startup_dialog namespace import ::pd_i18n::_ # hack - these should be better handled in the C code namespace import ::dialog_array::pdtk_array_listview_new namespace import ::dialog_array::pdtk_array_listview_fillpage namespace import ::dialog_array::pdtk_array_listview_setpage namespace import ::dialog_array::pdtk_array_listview_closeWindow #------------------------------------------------------------------------------# # global variables # this is a wide array of global variables that are used throughout the GUI. # they can be used in plugins to check the status of various things since they # should all have been properly initialized by the time startup plugins are # loaded. set PD_MAJOR_VERSION 0 set PD_MINOR_VERSION 0 set PD_BUGFIX_VERSION 0 set PD_TEST_VERSION "" set done_init 0 # for testing which platform we are running on ("aqua", "win32", or "x11") set windowingsystem "" # args about how much and where to log set loglevel 2 set stderr 0 #args to pass to pd if we're starting it up set pd_startup_args "" # connection between 'pd' and 'pd-gui' set host "" set port 0 # canvas font, received from pd in pdtk_pd_startup, set in s_main.c set font_family "courier" set font_weight "normal" # sizes of chars for each of the Pd fixed font sizes: # width(pixels) height(pixels) set font_metrics { 5 11 6 13 7 16 10 19 14 29 22 44 } # sizes as above for zoomed-in view set font_zoom2_metrics { 10 22 12 26 14 32 20 38 28 58 44 88 } set font_measured {} set font_zoom2_measured {} # root path to lib of Pd's files, see s_main.c for more info set sys_libdir {} # root path where the pd-gui.tcl GUI script is located set sys_guidir {} # user-specified search paths for objects, help, fonts, etc. set sys_searchpath {} # user-specified search paths from the commandline -path option set sys_temppath {} # hard-coded search paths for objects, help, plugins, etc. set sys_staticpath {} # the path to the folder where the current plugin is being loaded from set current_plugin_loadpath {} # a list of plugins that were loaded set loaded_plugins {} # list of command line flags set at startup set sys_flags {} # list of libraries loaded on startup set startup_libraries {} # start dirs for new files and open panels set filenewdir [pwd] set fileopendir [pwd] # lists of audio/midi devices and APIs for prefs dialogs set audio_apilist {} set audio_indevlist {} set audio_outdevlist {} set midi_apilist {} set midi_indevlist {} set midi_outdevlist {} set pd_whichapi 0 set pd_whichmidiapi 0 # current state of the DSP set dsp 0 # state of the peak meters in the Pd window set meters 0 # the toplevel window that currently is on top and has focus set focused_window . # store that last 5 files that were opened set recentfiles_list {} set total_recentfiles 5 # modifier for key commands (Ctrl/Control on most platforms, Cmd/Mod1 on MacOSX) set modifier "" # current state of the Edit Mode menu item set editmode_button 0 ## per toplevel/patch data # window location modifiers set menubarsize 0 ;# Mac OS X and other platforms have a menubar on top set windowframex 0 ;# different platforms have different window frames set windowframey 0 ;# different platforms have different window frames # patch properties array set editmode {} ;# store editmode for each open patch canvas array set editingtext {};# if an obj, msg, or comment is being edited, per patch array set loaded {} ;# store whether a patch has completed loading array set xscrollable {};# keep track of whether the scrollbars are present array set yscrollable {} # patch window tree, these might contain patch IDs without a mapped toplevel array set windowname {} ;# window names based on mytoplevel IDs array set childwindows {} ;# all child windows based on mytoplevel IDs array set parentwindows {} ;# topmost parent window ID based on mytoplevel IDs # variables for holding the menubar to allow for configuration by plugins set ::pdwindow_menubar ".menubar" set ::patch_menubar ".menubar" set ::dialog_menubar "" # minimum size of the canvas window of a patch set canvas_minwidth 50 set canvas_minheight 20 # undo states array set undo_actions {} array set redo_actions {} # unused legacy undo states set undo_action no set redo_action no namespace eval ::pdgui:: { variable scriptname [ file normalize [ info script ] ] } #------------------------------------------------------------------------------# # coding style # # these are preliminary ideas, we'll change them as we work things out: # - when possible use "" doublequotes to delimit messages # - use '$::myvar' instead of 'global myvar' # - for the sake of clarity, there should not be any inline code, everything # should be in a proc that is ultimately triggered from main() # - if a menu_* proc opens a dialog panel, that proc is called menu_*_dialog # - use "eq/ne" for string comparison, NOT "==/!=" (http://wiki.tcl.tk/15323) # # ## Names for Common Variables #---------------------------- # variables named after the Tk widgets they represent # $window = any kind of Tk widget that can be a Tk 'window' # $mytoplevel = a window id made by a 'toplevel' command # $gfxstub = a 'toplevel' window id for dialogs made in gfxstub/x_gui.c # $menubar = the 'menu' attached to each 'toplevel' # $mymenu = 'menu' attached to the menubar, like the File menu # $tkcanvas = a Tk 'canvas', which is the root of each patch # # ## Dialog Panel Types #---------------------------- # global (only one): find, sendmessage, prefs, helpbrowser # per-canvas: font, canvas properties (created with a message from pd) # per object: gatom, iemgui, array, data structures (created with a message from pd) # # ## Prefix Names for procs #---------------------------- # pdtk_ pd -> pd-gui API (i.e. called from 'pd') # pdsend pd-gui -> pd API (sends a message to 'pd' using pdsend) # ------------------------------------------------------------------------------ # init functions # root paths to find Pd's files where they are installed proc set_pd_paths {} { set ::sys_guidir [file normalize [file dirname [info script]]] set ::sys_libdir [file normalize [file join $::sys_guidir ".."]] } proc init_for_platform {} { # we are not using Tk scaling, so fix it to 1 on all platforms. This # guarantees that patches will be pixel-exact on every platform # 2013.07.19 msp - trying without this to see what breaks - it's having # deleterious effects on dialog window font sizes. # tk scaling 1 switch -- $::windowingsystem { "x11" { set ::modifier "Control" option add *PatchWindow*Canvas.background "white" startupFile # add control to show/hide hidden files in the open panel (load # the tk_getOpenFile dialog once, otherwise it will not work) catch {tk_getOpenFile -with-invalid-argument} set ::tk::dialog::file::showHiddenBtn 1 set ::tk::dialog::file::showHiddenVar 0 # set file types that open/save recognize set ::filetypes \ [list \ [list [_ "Associated Files"] {.pd .pat .mxt} ] \ [list [_ "Pd Files"] {.pd} ] \ [list [_ "Max Patch Files"] {.pat} ] \ [list [_ "Max Text Files"] {.mxt} ] \ ] # some platforms have a menubar on the top, so place below them set ::menubarsize 0 # trying loading icon in the GUI directory if {$::tcl_version >= 8.5} { set icon [file join $::sys_guidir pd.gif] if {[file readable $icon]} { catch { wm iconphoto . -default [image create photo -file "$icon"] } } } # mouse cursors for all the different modes set ::cursor_runmode_nothing "left_ptr" set ::cursor_runmode_clickme "arrow" set ::cursor_runmode_thicken "sb_v_double_arrow" set ::cursor_runmode_addpoint "plus" set ::cursor_editmode_nothing "hand2" set ::cursor_editmode_connect "circle" set ::cursor_editmode_disconnect "X_cursor" set ::cursor_editmode_resize "sb_h_double_arrow" } "aqua" { # load tk::mac event callbacks here, this way launching pd # from the commandline incorporates the special mac event handling package require apple_events set ::modifier "Mod1" if {$::tcl_version < 8.5} { # old default font for Tk 8.4 on macOS # since font detection requires 8.5+ set ::font_family "Monaco" } else { # hack until DVSM bug is fixed on macOS 10.15+ set ::font_family "Menlo" } option add *DialogWindow*background "#E8E8E8" startupFile option add *DialogWindow*Entry.highlightBackground "#E8E8E8" startupFile option add *DialogWindow*Button.highlightBackground "#E8E8E8" startupFile option add *DialogWindow*Entry.background "white" startupFile option add *DialogWindow*Menu.foreground "black" startupFile # Mac OS X needs a menubar all the time set ::dialog_menubar ".menubar" # set file types that open/save recognize set ::filetypes \ [list \ [list [_ "Associated Files"] {.pd .pat .mxt} ] \ [list [_ "Pd Files"] {.pd} ] \ [list [_ "Max Patch Files (.pat)"] {.pat} ] \ [list [_ "Max Text Files (.mxt)"] {.mxt} ] \ ] # some platforms have a menubar on the top, so place below them set ::menubarsize 22 # mouse cursors for all the different modes set ::cursor_runmode_nothing "arrow" set ::cursor_runmode_clickme "center_ptr" set ::cursor_runmode_thicken "sb_v_double_arrow" set ::cursor_runmode_addpoint "plus" set ::cursor_editmode_nothing "hand2" set ::cursor_editmode_connect "circle" set ::cursor_editmode_disconnect "X_cursor" set ::cursor_editmode_resize "sb_h_double_arrow" } "win32" { set ::modifier "Control" option add *PatchWindow*Canvas.background "white" startupFile # fix menu font size on Windows with tk scaling = 1 font create menufont -family Tahoma -size -11 option add *Menu.font menufont startupFile option add *HelpBrowser*font menufont startupFile option add *DialogWindow*font menufont startupFile option add *PdWindow*font menufont startupFile option add *ErrorDialog*font menufont startupFile # set file types that open/save recognize set ::filetypes \ [list \ [list [_ "Associated Files"] {.pd .pat .mxt} ] \ [list [_ "Pd Files"] {.pd} ] \ [list [_ "Max Patch Files"] {.pat} ] \ [list [_ "Max Text Files"] {.mxt} ] \ ] # some platforms have a menubar on the top, so place below them set ::menubarsize 0 # TODO use 'winico' package for full, hicolor icon support wm iconbitmap . -default [file join $::sys_guidir pd.ico] # add local fonts to Tk's font list using pdfontloader if {[file exists [file join "$::sys_libdir" "font"]]} { catch { load [file join "$::sys_libdir" "bin/pdfontloader.dll"] set localfonts {"DejaVuSansMono.ttf" "DejaVuSansMono-Bold.ttf" "DejaVuSansMono-Oblique.ttf" "DejaVuSansMono-BoldOblique.ttf"} foreach font $localfonts { set path [file join "$::sys_libdir" "font/$font"] pdfontloader::load $path ::pdwindow::verbose 0 "pdfontloader loaded [file tail $path]\n" } } } # mouse cursors for all the different modes set ::cursor_runmode_nothing "right_ptr" set ::cursor_runmode_clickme "arrow" set ::cursor_runmode_thicken "sb_v_double_arrow" set ::cursor_runmode_addpoint "plus" set ::cursor_editmode_nothing "hand2" set ::cursor_editmode_connect "circle" set ::cursor_editmode_disconnect "X_cursor" set ::cursor_editmode_resize "sb_h_double_arrow" } } } # ------------------------------------------------------------------------------ # locale handling proc load_locale {} { ::pd_i18n::load_locale ##--moo: force default system and stdio encoding to UTF-8 encoding system utf-8 fconfigure stderr -encoding utf-8 fconfigure stdout -encoding utf-8 ##--/moo } # ------------------------------------------------------------------------------ # font handling # this proc gets the internal font name associated with each size proc get_font_for_size {fsize} { return [list $::font_family -$fsize $::font_weight] } # searches for a font to use as the default. Tk automatically assigns a # monospace font to the name "Courier" (see Tk 'font' docs), but it doesn't # always do a good job of choosing in respect to Pd's needs. So this chooses # from a list of fonts that are known to work well with Pd. proc find_default_font {} { set testfonts { "DejaVu Sans Mono" "Bitstream Vera Sans Mono" "Menlo" "Monaco" \ "Inconsolata" "Courier 10 Pitch" "Andale Mono" "Droid Sans Mono" } foreach family $testfonts { if {[lsearch -exact -nocase [font families] $family] > -1} { set ::font_family $family break } } set msg [_ "detected font: %s" ] ::pdwindow::verbose 0 [format "${msg}\n" $::font_family ] } proc set_base_font {family weight} { if {[lsearch -exact [font families] $family] > -1} { set ::font_family $family } else { set msg [_ "WARNING: font family '%1\$s' not found, using default (%2\$s)"] ::pdwindow::post [format "${msg}\n" $family $::font_family] } if {[lsearch -exact {bold normal} $weight] > -1} { set ::font_weight $weight set using_defaults 0 } else { set msg [_ "WARNING: font weight '%1\$s' not found, using default (%2\$s)"] ::pdwindow::post [format "${msg}\n" $weight $::font_weight] } set msg [_ "using font: %1\$s %2\$s" ] ::pdwindow::verbose 0 [ format "${msg}\n" $::font_family $::font_weight ] } # finds sizes of the chosen font that just fit into the required metrics # e.g. if the metric requires the 'M' to be 15x10 pixels, # and the given font at size 12 is 15x7 and at size 16 it is 19x10, # then we would pick size 12. # is a list of pairs for which we are seeking font-sizes. # the proc returns a list of matching ... proc fit_font_into_metrics {family weight wantmetrics} { set lastsize 0 set lastwidth 0 set lastheight 0 set result {} if { [llength $wantmetrics] < 2} { return $result } for {set fsize 1} {$fsize < 120} {incr fsize} { set foo [list $family -$fsize $weight] set height [font metrics $foo -linespace] set width [font measure $foo M] if { $lastsize < 1 } { # just in case even the smallest font does not fit, # we just use it nevertheless set lastsize $fsize set lastwidth $width set lastheight $height } if { $width > [lindex $wantmetrics 0] || $height > [lindex $wantmetrics 1] } { # oops, this font is already too big; use the last one lappend result $lastsize $lastwidth $lastheight # and search for the next one set wantmetrics [lrange $wantmetrics 2 end] if { [llength $wantmetrics] < 2} { break } } set lastsize $fsize set lastwidth $width set lastheight $height } return $result } # ------------------------------------------------------------------------------ # procs called directly by pd proc pdtk_pd_startup {major minor bugfix test audio_apis midi_apis sys_font sys_fontweight} { set ::PD_MAJOR_VERSION $major set ::PD_MINOR_VERSION $minor set ::PD_BUGFIX_VERSION $bugfix set ::PD_TEST_VERSION $test set oldtclversion 0 set ::audio_apilist $audio_apis set ::midi_apilist $midi_apis ::pdwindow::verbose 0 "Tk [info patchlevel]\n" if {$::tcl_version >= 8.5} {find_default_font} set_base_font $sys_font $sys_fontweight set ::font_measured [fit_font_into_metrics $::font_family $::font_weight $::font_metrics] set ::font_zoom2_measured [fit_font_into_metrics $::font_family $::font_weight $::font_zoom2_metrics] ::pd_bindings::class_bindings ::pd_bindings::global_bindings ::pd_menus::create_menubar ::pdwindow::create_window ::pdwindow::configure_menubar ::pd_menus::configure_for_pdwindow ::pdwindow::create_window_finalize load_startup_plugins pdsend "pd init [enquote_path [pwd]] $oldtclversion \ $::font_measured $::font_zoom2_measured" open_filestoopen set ::done_init 1 } ##### routine to ask user if OK and, if so return '1' (or else '0') # (this really should be in some other file) proc pdtk_yesnodialog {mytoplevel message default} { wm deiconify $mytoplevel raise $mytoplevel if {$::windowingsystem eq "win32"} { set answer [tk_messageBox -message [_ $message] -type yesno \ -default $default -icon question \ -title [wm title $mytoplevel]] } { set answer [tk_messageBox -message [_ $message] -type yesno \ -default $default -icon question \ -parent $mytoplevel] } if {$answer eq "yes"} { return 1 } return 0 } # routine to ask user if OK and, if so, send a message on to Pd ###### # with built-informatting+ translation # modern usage: ## pdtk_check .pdwindow {"Hello world!"} "pd dsp 1" no # legacy: ## pdtk_check .pdwindow "Hello world!" "pd dsp 1" no proc pdtk_check {mytoplevel message reply_to_pd default} { # example: 'pdtk_check . [list "Switch compatibility to %s?" $compat] [list pd compatibility $compat ] no' if {[lindex $message 0] == [lindex [lindex $message 0] 0]} { set message [ list $message ] } if {[ catch { set msg [_ {*}$message] } ]} { set msg [_ $message] } if {[ pdtk_yesnodialog $mytoplevel $msg $default ]} { pdsend $reply_to_pd } } # dispatch a message from running Pd patches to the intended plugin receiver proc pdtk_plugin_dispatch { args } { set receiver [ lindex $args 0 ] if [ info exists ::pd_connect::plugin_dispatch_receivers($receiver) ] { foreach callback $::pd_connect::plugin_dispatch_receivers($receiver) { $callback [ lrange $args 1 end ] } } } # ------------------------------------------------------------------------------ # parse command line args when Wish/pd-gui.tcl is started first proc parse_args {argc argv} { opt_parser::init { {-stderr set {::stderr}} {-open lappend {- ::filestoopen_list}} {-pdarg lappend {- ::pd_startup_args}} } set unflagged_files [opt_parser::get_options $argv] # if we have a single arg that is not a file, its a port or host:port combo if {$argc == 1 && ! [file exists [ lindex $argv 0 ]]} { set arg1 [ lindex $argv 0 ] if { [string is int $arg1] && $arg1 > 0} { # 'pd-gui' got the port number from 'pd' set ::host "localhost" set ::port $arg1 } else { set hostport [split $arg1 ":"] set ::port [lindex $hostport 1] if { [string is int $::port] && $::port > 0} { set ::host [lindex $hostport 0] } else { set ::port 0 } } } elseif {$unflagged_files ne ""} { foreach filename $unflagged_files { lappend ::filestoopen_list $filename } } } proc open_filestoopen {} { foreach filename $::filestoopen_list { open_file $filename } } # ------------------------------------------------------------------------------ # X11 procs for handling singleton state and getting args from other instances # first instance proc singleton {key} { if {![catch { selection get -selection $key }]} { return 0 } selection handle -selection $key . "singleton_request" selection own -command first_lost -selection $key . return 1 } proc singleton_request {offset maxbytes} { ## the next 2 lines raise the focus to the given window (and change desktop) # wm deiconify .pdwindow # raise .pdwindow return [tk appname] } proc first_lost {} { receive_args [selection get -selection ${::pdgui::scriptname} ] selection own -command first_lost -selection ${::pdgui::scriptname} . } proc others_lost {} { set ::singleton_state "exit" destroy . exit } # all other instances proc send_args {offset maxChars} { set sendargs {} foreach filename $::filestoopen_list { lappend sendargs [file normalize $filename] } return [string range $sendargs $offset [expr {$offset+$maxChars}]] } # this command will open files received from a 2nd instance of Pd proc receive_args {filelist} { raise . wm deiconify .pdwindow raise .pdwindow foreach filename $filelist { open_file $filename } } proc dde_open_handler {cmd} { open_file [file normalize $cmd] } proc check_for_running_instances { } { # if pd-gui gets called from pd ('pd-gui 5400') or is told otherwise # to connect to a running instance of Pd (by providing [:]) # then we don't want to connect to a running instance if { $::port > 0 && $::host ne "" } { return } switch -- $::windowingsystem { "aqua" { # handled by ::tk::mac::OpenDocument in apple_events.tcl } "x11" { # http://wiki.tcl.tk/1558 # TODO replace PUREDATA name with path so this code is a singleton # based on install location rather than this hard-coded name if {![singleton ${::pdgui::scriptname}_MANAGER ]} { selection handle -selection ${::pdgui::scriptname} . "send_args" selection own -command others_lost -selection ${::pdgui::scriptname} . after 5000 set ::singleton_state "timeout" vwait ::singleton_state exit } else { # first instance selection own -command first_lost -selection ${::pdgui::scriptname} . } } "win32" { ## http://wiki.tcl.tk/8940 package require dde ;# 1.4 or later needed for full unicode support set topic "Pure_Data_DDE_Open ${::pdgui::scriptname}" # if no DDE service is running, start one and claim the name if { [dde services TclEval $topic] == {} } { # registers the interpreter as a DDE server with the service name 'TclEval' and the topic name specified by 'topic' dde servername -handler dde_open_handler $topic } else { # DDE is already running: use it to open the file with the running instance # we only open a single file (assuming that this is called by double-clicking) set filename [lindex ${::argv} 0] dde eval $topic $filename exit 0 } } } } # ------------------------------------------------------------------------------ # load plugins on startup proc load_plugin_script {filename} { global errorInfo set basename [file tail $filename] if {[lsearch $::loaded_plugins $basename] > -1} { ::pdwindow::post [ format [_ "'%1\$s' already loaded, ignoring: '%2\$s'"] $basename $filename] ::pdwindow::post "\n" return } ::pdwindow::debug [ format [_ "Loading plugin: %s"] $filename ] ::pdwindow::debug "\n" set tclfile [open $filename] set tclcode [read $tclfile] close $tclfile if {[catch {uplevel #0 $tclcode} errorname]} { ::pdwindow::error "-----------\n" ::pdwindow::error [ format [_ "UNHANDLED ERROR: %s"] $errorInfo ] ::pdwindow::error "\n" ::pdwindow::error [ format [_ "FAILED TO LOAD %s"] $filename ] ::pdwindow::error "\n-----------\n" } else { lappend ::loaded_plugins $basename } } proc load_startup_plugins {} { # load built-in plugins load_plugin_script [file join $::sys_guidir pd_deken.tcl] if { $::port > 0 && $::host ne "" } { } else { # only run the docsdir plugin if Pd is started via the GUI # (to prevent a dialog from popping up on systems without keyboard/mouse) load_plugin_script [file join $::sys_guidir pd_docsdir.tcl] } # load other installed plugins foreach pathdir [concat $::sys_temppath $::sys_searchpath $::sys_staticpath] { set dir [file normalize $pathdir] if { ! [file isdirectory $dir]} {continue} if { [ catch { foreach filename [glob -directory $dir -nocomplain -types {f} -- \ *-plugin/*-plugin.tcl *-plugin.tcl] { set ::current_plugin_loadpath [file dirname $filename] if { [ catch { load_plugin_script $filename } ] } { ::pdwindow::debug [ format [ _ "Failed to read plugin %s ...skipping!"] $filename ] ::pdwindow::debug "\n" } } } ] } { # this is triggered in weird cases, e.g. when ~/Documents/Pd/externals is not readable, # but ~/Documents/Pd/ is... ::pdwindow::debug [ format [_ "Failed to find plugins in %s ...skipping!" ] $dir ] ::pdwindow::debug "\n" } } } # ------------------------------------------------------------------------------ # main proc main {argc argv} { tk appname pd-gui set ::windowingsystem [tk windowingsystem] set ::platform $::tcl_platform(os) if { $::tcl_platform(platform) eq "windows"} { set ::platform W32 } ::pd_guiprefs::init ::pd_i18n::init parse_args $argc $argv check_for_running_instances set_pd_paths init_for_platform # ::host and ::port are parsed from argv by parse_args if { $::port > 0 } { if { $::host ne "" } { # 'pd' started a server and launched us, so connect to it as client ::pd_connect::to_pd $::port $::host } else { # wait for a client 'pd' to connect to us; we're the server # to do this, invoke as "pd-gui.tcl :1234" and start pd separately. ::pd_connect::create_socket $::port } } else { # the GUI is starting first, so create socket and exec 'pd' set ::port [::pd_connect::create_socket 0] set ::pd_startup_args \ [string map {\{ "" \} ""} $::pd_startup_args] set basedir [file normalize [file join [file dirname [info script]] ..] ] set exe_floatsize [::pd_guiprefs::read "pdcore_precision_binary" ] set pid "" set stderr "" # the second-from-last and next-to-last entries are just fallbacks # in case there's no 'pd' # the very last entry is repeating a previous one, # in order to have 'catch' emit a less confusing error message into $stderr foreach {bindir exe} [list \ bin${exe_floatsize} pd${exe_floatsize} \ bin${exe_floatsize} pd \ bin pd${exe_floatsize} \ bin pd \ bin pd32 \ bin pd64 \ bin pd \ ] { set pd_exec [file join $basedir $bindir $exe] catch { set pid "" set pid [exec -- $pd_exec -guiport $::port {*}$::pd_startup_args &] break } stderr if { $pid != "" } { break } } if { $pid eq "" } { # Pd failed to start # give a nice error dialog and quit if {$::tcl_version >= 8.5} { tk_messageBox \ -title [_ "Pd startup failure" ] \ -message [_ "Failed to start Pd-core" ] \ -detail "$stderr" \ -icon error \ -type ok } else { tk_messageBox \ -title [_ "Pd startup failure" ] \ -message [_ "Failed to start Pd-core" ] \ -icon error \ -type ok } exit 1 } ::pd_connect::set_pid $pid # if 'pd-gui' first, then initial dir is home set ::filenewdir $::env(HOME) set ::fileopendir $::env(HOME) } ::pdwindow::verbose 0 "------------------ done with main ----------------------\n" } main $::argc $::argv