diff --git a/.gitignore b/.gitignore index 15c253c24..c04f119b7 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ /*.tds /*.td2 /*.map -/Makefile.bor /Makefile.mgw /Makefile.vc /Makefile.lcc @@ -128,7 +127,6 @@ /windows/*.td2 /windows/*.map /windows/Makefile.clangcl -/windows/Makefile.bor /windows/Makefile.mgw /windows/Makefile.vc /windows/Makefile.lcc diff --git a/Buildscr b/Buildscr index 7ca4eb7f5..8833a9c49 100644 --- a/Buildscr +++ b/Buildscr @@ -35,7 +35,7 @@ module putty ifeq "$(RELEASE)" "" set Ndate $(!builddate) ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -pe 's/(....)(..)(..)/$$1-$$2-$$3/' > date ifneq "$(Ndate)" "" read Date date -set Epoch 16214 # update this at every release +set Epoch 16351 # update this at every release ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -ne 'use Time::Local; /(....)(..)(..)/ and print timegm(0,0,0,$$3,$$2-1,$$1) / 86400 - $(Epoch)' > days ifneq "$(Ndate)" "" read Days days @@ -139,8 +139,7 @@ ifneq "$(MAKEARGS)" "" set Makeargs $(Makeargs) $(MAKEARGS) in putty do ./mksrcarc.sh in putty do ./mkunxarc.sh '$(Autoconfver)' '$(Uxarcsuffix)' $(Docmakever) in putty do perl mkfiles.pl -in putty/doc do make $(Docmakever) putty.hlp -in putty/doc do make $(Docmakever) chm +in putty/doc do make $(Docmakever) putty.hlp putty.chm # Munge the installer script locally so that it reports the version # we're really building. @@ -157,50 +156,38 @@ in putty/icons do make in putty do convert -size 164x312 'gradient:blue-white' -distort SRT -90 -swirl 180 \( -size 329x312 canvas:white \) +append \( icons/putty-48.png -geometry +28+24 \) -composite \( icons/pscp-48.png -geometry +88+96 \) -composite \( icons/puttygen-48.png -geometry +28+168 \) -composite \( icons/pageant-48.png -geometry +88+240 \) -composite windows/msidialog.bmp in putty do convert -size 493x58 canvas:white \( icons/putty-48.png -geometry +440+5 \) -composite windows/msibanner.bmp -delegate windows - # Build the original binaries. - in putty/windows with visualstudio do/win mkdir buildold && nmake -f Makefile.vc BUILDDIR=buildold\ $(Makeargs) all cleantestprogs - - # Build the VS2015 binaries. For the 32-bit ones, we set a subsystem - # version of 5.01, which allows the resulting files to still run on - # Windows XP. - in putty/windows with visualstudio2015_32bit do/win mkdir build32 && nmake -f Makefile.vc BUILDDIR=build32\ SUBSYSVER=,5.01 $(Makeargs) all cleantestprogs - in putty/windows with visualstudio2015_64bit do/win mkdir build64 && nmake -f Makefile.vc BUILDDIR=build64\ $(Makeargs) all cleantestprogs - - # Code-sign the binaries, if the local bob config provides a script - # to do so. We assume here that the script accepts an -i option to - # provide a 'more info' URL, and an optional -n option to provide a - # program name, and that it can take multiple .exe filename - # arguments and sign them all in place. - ifneq "$(winsigncode)" "" in putty/windows do $(winsigncode) -i http://www.chiark.greenend.org.uk/~sgtatham/putty/ build*/*.exe - - # Ignore exit code from hhc, in favour of seeing whether the .chm - # file was created. (Yuck; but hhc appears to return non-zero - # exit codes on whim.) - in putty/doc with htmlhelp do/win hhc putty.hhp & type putty.chm >nul - - # Build a WiX MSI installer, for each of build32 and build64. - in putty/windows with wix do/win candle -arch x86 -dWin64=no -dBuilddir=build32\ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer32.msi - in putty/windows with wix do/win candle -arch x64 -dWin64=yes -dBuilddir=build64\ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer64.msi - - # Build the old Inno Setup installer, for 32-bit only. - in putty/windows with innosetup do/win iscc putty.iss - - # Sign the installers. - ifneq "$(winsigncode)" "" in putty/windows do $(winsigncode) -i http://www.chiark.greenend.org.uk/~sgtatham/putty/ -n "PuTTY Installer" installer32.msi installer64.msi Output/installer.exe +# Build the standard binaries, in both 32- and 64-bit flavours. +# +# For the 32-bit ones, we set a subsystem version of 5.01, which +# allows the resulting files to still run on Windows XP. +in putty/windows with clangcl32 do mkdir build32 && Platform=x86 make -f Makefile.clangcl BUILDDIR=build32/ SUBSYSVER=,5.01 $(Makeargs) all cleantestprogs +in putty/windows with clangcl64 do mkdir build64 && Platform=x64 make -f Makefile.clangcl BUILDDIR=build64/ $(Makeargs) all cleantestprogs + +# Build the 'old' binaries, which should still run on all 32-bit +# versions of Windows back to Win95 (but not Win32s). These link +# against Visual Studio 2003 libraries (the more modern versions +# assume excessively modern Win32 API calls to be available), specify +# a subsystem version of 4.0, and compile with /arch:IA32 to prevent +# the use of modern CPU features like MMX which older machines also +# might not have. +in putty/windows with clangcl32_2003 do mkdir buildold && Platform=x86 make -f Makefile.clangcl BUILDDIR=buildold/ $(Makeargs) CCTARGET=i386-pc-windows-msvc13.0.0 SUBSYSVER=,4.0 EXTRA_windows=wincrt0.obj EXTRA_console=crt0.obj XFLAGS=/arch:IA32 all cleantestprogs + +# Code-sign the Windows binaries, if the local bob config provides a +# script to do so in a cross-compiling way. We assume here that the +# script accepts an -i option to provide a 'more info' URL, an +# optional -n option to provide a program name, and an -N option to +# take the program name from an .exe's version resource, and that it +# can accept multiple .exe or .msi filename arguments and sign them +# all in place. +ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ build*/*.exe + +# Build a WiX MSI installer, for each of build32 and build64. +in putty/windows with wixonlinux do candle -arch x86 -dWin64=no -dBuilddir=build32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer32.msi -spdb +in putty/windows with wixonlinux do candle -arch x64 -dWin64=yes -dBuilddir=build64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer64.msi -spdb + +# Sign the Windows installers. +ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ -n "PuTTY Installer" installer32.msi installer64.msi - # Finished Windows builds. - return putty/windows/buildold/*.exe - return putty/windows/buildold/*.map - return putty/windows/build32/*.exe - return putty/windows/build32/*.map - return putty/windows/build64/*.exe - return putty/windows/build64/*.map - return putty/doc/putty.chm - return putty/windows/installer32.msi - return putty/windows/installer64.msi - return putty/windows/Output/installer.exe -enddelegate in putty/doc do make mostlyclean in putty/doc do make $(Docmakever) in putty/windows/buildold do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm ../../doc/putty.hlp ../../doc/putty.cnt @@ -217,7 +204,6 @@ deliver putty/windows/build64/*.exe putty/w64/$@ deliver putty/windows/build64/putty.zip putty/w64/$@ deliver putty/windows/installer32.msi putty/w32/$(Ifilename32).msi deliver putty/windows/installer64.msi putty/w64/$(Ifilename64).msi -deliver putty/windows/Output/installer.exe putty/w32/$(Ifilename32).exe deliver putty/doc/puttydoc.zip putty/$@ deliver putty/doc/putty.chm putty/$@ deliver putty/doc/putty.hlp putty/$@ diff --git a/CHECKLST.txt b/CHECKLST.txt index 0499d7802..21f386562 100644 --- a/CHECKLST.txt +++ b/CHECKLST.txt @@ -24,25 +24,28 @@ pre-releases on the website: add a news announcement in components/news. (Previous naming convention has been to name it in the form 'X.YZ-pre.mi'.) -Preparing to make a release ---------------------------- +Things to do during the branch-stabilisation period: -Now that PuTTY is in git, a lot of the release preparation can be done -in advance, in local checkouts, and not pushed until the actual -process of _releasing_ it. + - Go through the source (including the documentation), and the + website, and review anything tagged with a comment containing the + word XXX-REVIEW-BEFORE-RELEASE. (Any such comments should state + clearly what needs to be done.) -To begin with, before dropping the tag, make sure everything is ready -for it: + - Do some testing of the Windows version with Minefield (you can + build a Minefield version using 'bob . XFLAGS=-DMINEFIELD'), and of + the Unix version with valgrind. In particular, any headline + features for the release should get a workout with memory checking + enabled! - - First of all, go through the source (including the documentation), - and the website, and review anything tagged with a comment - containing the word XXX-REVIEW-BEFORE-RELEASE. - (Any such comments should state clearly what needs to be done.) +Making a release candidate build +-------------------------------- - - Also, do some testing of the Windows version with Minefield, and - of the Unix version with valgrind or efence or both. In - particular, any headline features for the release should get a - workout with memory checking enabled! + - Make a directory to hold all the release paraphernalia. I usually + call it ~/src/putty/X.YZ (where X.YZ will stand throughout for the + version number). In that directory, make a git clone of the PuTTY + repository, where you can make release-related commits and tags + tentatively, and keep them out of the way of any 'git push' you + might still be doing in other checkouts. - Double-check that we have removed anything tagged with a comment containing the words XXX-REMOVE-BEFORE-RELEASE or @@ -50,9 +53,9 @@ for it: hits in this file itself.) - Now update the version numbers and the transcripts in the docs, by - checking out the release branch and running + checking out the release branch in the release-specific checkout + and running - make distclean ./release.pl --version=X.YZ --setver Then check that the resulting automated git commit has updated the @@ -72,6 +75,42 @@ for it: - If the release is on a branch (which I expect it generally will be), merge that branch to master. + - Make a release-candidate build from the release tag, and put the + build.out and build.log dfiles somewhere safe. Normally I store + these in an adjacent directory, so I'll run a command like + bob -o ../X.YZ/build-X.YZ-rcN.out -l ../X.YZ/build-X.YZ-rcN.log -c X.YZ . RELEASE=X.YZ + This should generate a basically valid release directory as + `build-X.YZ-rcN.out/putty', and provide link maps and sign.sh + alongside that. + + - Double-check in build-X.YZ-rcN.log that the release was built from + the right git commit. + + - Make a preliminary gpg signature, but don't run the full release- + signing procedure. (We use the presence of a full set of GPG + signatures to distinguish _abandoned_ release candidates from the + one that ended up being the release.) In the 'build.X.YZ-rcN.out' + directory, run + sh sign.sh -r -p putty + and you should only have to enter the release key passphrase once, + which will generate a clearsigned file called + sha512sums-preliminary.gpg _outside_ the 'putty' subdirectory. + + - For my own safety, make the release candidate build read-only. + chmod -R a-w build-X.YZ-rcN.out build-X.YZ-rcN.log + + - Now do some checking of the release binaries, and pass them to the + rest of the team to do some as well. Do at least these things: + * make sure they basically work + * check they report the right version number + * if there's any easily observable behaviour difference between + the release branch and master, arrange to observe it + * test the Windows installer + * test the Unix source tarball. + +Preparing to make the release +----------------------------- + - Write a release announcement (basically a summary of the changes since the last release). Squirrel it away in thyestes:src/putty-local/announce- in case it's needed again @@ -96,33 +135,16 @@ for it: branch (so that the wishlist mechanism can't automatically mark them as fixed in the new release), add appropriate Fixed-in headers for those. - * Add an entry to the @releases array in control/bugs2html. - - - Make a release-candidate build from the release tag, and put the - build.out and build.log dfiles somewhere safe. Normally I store - these in an adjacent directory, so I'll run a command like - bob -o ../X.YZ/build-X.YZ-rcN.out -l ../X.YZ/build-X.YZ-rcN.log -c X.YZ . RELEASE=X.YZ - This should generate a basically valid release directory as - `build-X.YZ-rcN.out/putty', and provide link maps and sign.sh - alongside that. - - - Double-check in build-X.YZ-rcN.log that the release was built from - the right git commit. - - Do a bit of checking of the release binaries: - * make sure they basically work - * check they report the right version number - * if there's any easily observable behaviour difference between - the release branch and master, arrange to observe it - * test the Windows installer - * test the Unix source tarball. - - - Sign the release: in the `build-X.YZ-rcN.out' directory, type + - Sign the release in full. In the `build-X.YZ-rcN.out' directory, + re-verify that the preliminary signed checksums file has a correct + signature on it and also matches the files you're about to sign for real: + gpg -d sha512sums-preliminary.gpg | (cd putty; sha512sum -c) + If the combined output of that pipeline reports both a good + signature (from the release key) and a successful verification of + all the sha512sums, then all is well, so now run sh sign.sh -r putty - and enter the passphrases a lot of times. - - - For my own safety, make the release candidate build read-only. - chmod -R a-w build-X.YZ-rcN.out build-X.YZ-rcN.log + and enter the release key passphrase a lot of times. The actual release procedure ---------------------------- diff --git a/LATEST.VER b/LATEST.VER index db1ed30c7..ac37bbeae 100644 --- a/LATEST.VER +++ b/LATEST.VER @@ -1 +1 @@ -0.68 +0.70 diff --git a/LICENCE b/LICENCE index 7c49ceb3b..c473a6a40 100644 --- a/LICENCE +++ b/LICENCE @@ -3,7 +3,9 @@ PuTTY is copyright 1997-2017 Simon Tatham. Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus -Kuhn, Colin Watson, Christopher Staite, and CORE SDI S.A. +Kuhn, Colin Watson, Christopher Staite, Lorenz Diener, Christian +Brabandt, Jeff Smith, Pavel Kryukov, Maxim Kuznetsov, Svyatoslav +Kuzmich, and CORE SDI S.A. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files diff --git a/README b/README index ef931dd14..de6eb9b06 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -This is the README for the source archive of PuTTY, a free Win32 +This is the README for the source archive of PuTTY, a free Windows and Unix Telnet and SSH client. If you want to rebuild PuTTY from source, we provide a variety of @@ -34,10 +34,6 @@ For building on Windows: MSVC/putty/putty.dsp builds PuTTY itself, MSVC/plink/plink.dsp builds Plink, and so on. - - windows/Makefile.bor is for the Borland C compiler. Type `make -f - Makefile.bor' while in the `windows' subdirectory to build all - the PuTTY binaries. - - windows/Makefile.mgw is for MinGW / Cygwin installations. Type `make -f Makefile.mgw' while in the `windows' subdirectory to build all the PuTTY binaries. @@ -127,11 +123,11 @@ Documentation (in various formats including Windows Help and Unix `man' pages) is built from the Halibut (`.but') files in the `doc' subdirectory using `doc/Makefile'. If you aren't using one of our source snapshots, you'll need to do this yourself. Halibut can be -found at . +found at . The PuTTY home web site is - http://www.chiark.greenend.org.uk/~sgtatham/putty/ + https://www.chiark.greenend.org.uk/~sgtatham/putty/ If you want to send bug reports or feature requests, please read the Feedback section of the web site before doing so. Sending one-line diff --git a/Recipe b/Recipe index 54e006366..b3739b977 100644 --- a/Recipe +++ b/Recipe @@ -16,7 +16,6 @@ !makefile vc windows/Makefile.vc !makefile vcproj windows/MSVC !makefile cygwin windows/Makefile.mgw -!makefile borland windows/Makefile.bor !makefile lcc windows/Makefile.lcc !makefile gtk unix/Makefile.gtk !makefile unix unix/Makefile.ux @@ -209,9 +208,9 @@ endif if HAVE_QUARTZ noinst_SCRIPTS = unix/PuTTY.app unix/Pterm.app unix/PuTTY.app: unix/putty.bundle puttyapp osxlaunch - rm -rf $@ && gtk-mac-bundler $< + rm -rf $@ && PUTTY_GTK_PREFIX_FROM_MAKEFILE=$$(pkg-config --variable=prefix gtk+-3.0) gtk-mac-bundler $< unix/Pterm.app: unix/pterm.bundle ptermapp osxlaunch - rm -rf $@ && gtk-mac-bundler $< + rm -rf $@ && PUTTY_GTK_PREFIX_FROM_MAKEFILE=$$(pkg-config --variable=prefix gtk+-3.0) gtk-mac-bundler $< endif !end @@ -236,12 +235,13 @@ TERMINAL = terminal wcwidth ldiscucs logging tree234 minibidi # GUI front end and terminal emulator (putty, puttytel). GUITERM = TERMINAL window windlg winctrls sizetip winprint winutils - + wincfg sercfg winhelp winjump + + wincfg sercfg winhelp winjump sessprep # Same thing on Unix. UXTERM = TERMINAL uxcfg sercfg uxucs uxprint timing callback miscucs GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols gtkmisc xkeysym - + x11misc gtkcomm + + x11misc gtkcomm sessprep +GTKMAIN = gtkmain cmdline # Non-SSH back ends (putty, puttytel, plink). NONSSH = telnet raw rlogin ldisc pinger @@ -256,14 +256,14 @@ WINSSH = SSH winnoise wincapi winpgntc wingss winshare winnps winnpc UXSSH = SSH uxnoise uxagentc uxgss uxshare # SFTP implementation (pscp, psftp). -SFTP = sftp int64 logging +SFTP = sftp int64 logging cmdline # Miscellaneous objects appearing in all the network utilities (not # Pageant or PuTTYgen). MISC = timing callback misc version settings tree234 proxy conf be_misc WINMISC = MISC winstore winnet winhandl cmdline windefs winmisc winproxy + wintime winhsock errsock winsecur winucs miscucs -UXMISC = MISC uxstore uxsel uxnet uxpeer cmdline uxmisc uxproxy time +UXMISC = MISC uxstore uxsel uxnet uxpeer uxmisc uxproxy time # import.c and dependencies, for PuTTYgen-like utilities that have to # load foreign key files. @@ -273,8 +273,8 @@ IMPORT = import sshbcrypt sshblowf CHARSET = sbcsdat slookup sbcs utf8 toucs fromucs xenc mimeenc macenc localenc # Standard libraries. -LIBS = advapi32.lib user32.lib gdi32.lib comctl32.lib comdlg32.lib - + shell32.lib winmm.lib imm32.lib winspool.lib ole32.lib +LIBS = advapi32.lib user32.lib gdi32.lib comdlg32.lib + + shell32.lib imm32.lib ole32.lib # Network backend sets. This also brings in the relevant attachment # to proxy.c depending on whether we're crypto-avoidant or not. @@ -298,7 +298,7 @@ U_BE_NOSSH = be_nos_s uxser nocproxy putty : [G] GUITERM NONSSH WINSSH W_BE_ALL WINMISC winx11 putty.res LIBS puttytel : [G] GUITERM NONSSH W_BE_NOSSH WINMISC puttytel.res nogss LIBS plink : [C] winplink wincons NONSSH WINSSH W_BE_ALL logging WINMISC - + winx11 plink.res winnojmp noterm LIBS + + winx11 plink.res winnojmp sessprep noterm LIBS pscp : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC + pscp.res winnojmp LIBS psftp : [C] psftp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC @@ -316,16 +316,16 @@ puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version pterm : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg - + nogss gtkmain + + nogss GTKMAIN putty : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty - + xpmpucfg gtkmain + + xpmpucfg GTKMAIN puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_NOSSH + uxstore uxsignal CHARSET uxputty NONSSH UXMISC xpmputty xpmpucfg - + nogss gtkmain + + nogss GTKMAIN plink : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal - + ux_x11 noterm uxnogtk + + ux_x11 noterm uxnogtk sessprep cmdline PUTTYGEN_UNIX = sshrsag sshdssg sshprime sshdes sshbn sshmd5 version + sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc @@ -343,11 +343,11 @@ pageant : [X] uxpgnt uxagentc aqsync pageant sshrsa sshpubk sshdes sshbn + gtkask gtkmisc UXMISC ptermapp : [XT] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore - + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg - + nogss gtkapp + + uxsignal CHARSET uxpterm version time xpmpterm xpmptcfg + + nogss gtkapp nocmdline puttyapp : [XT] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty - + xpmpucfg gtkapp + + xpmpucfg gtkapp nocmdline osxlaunch : [UT] osxlaunch fuzzterm : [UT] UXTERM CHARSET misc version uxmisc uxucs fuzzterm time settings diff --git a/callback.c b/callback.c index c70dc53fb..84ea0c807 100644 --- a/callback.c +++ b/callback.c @@ -14,7 +14,7 @@ struct callback { void *ctx; }; -struct callback *cbhead = NULL, *cbtail = NULL; +struct callback *cbcurr = NULL, *cbhead = NULL, *cbtail = NULL; toplevel_callback_notify_fn_t notify_frontend = NULL; void *frontend = NULL; @@ -26,6 +26,30 @@ void request_callback_notifications(toplevel_callback_notify_fn_t fn, frontend = fr; } +void delete_callbacks_for_context(void *ctx) +{ + struct callback *newhead, *newtail; + + newhead = newtail = NULL; + while (cbhead) { + struct callback *cb = cbhead; + cbhead = cbhead->next; + if (cb->ctx == ctx) { + sfree(cb); + } else { + if (!newhead) + newhead = cb; + else + newtail->next = cb; + + newtail = cb; + } + } + + cbhead = newhead; + cbtail = newtail; +} + void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) { struct callback *cb; @@ -34,10 +58,18 @@ void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) cb->fn = fn; cb->ctx = ctx; - /* If the front end has requested notification of pending + /* + * If the front end has requested notification of pending * callbacks, and we didn't already have one queued, let it know - * we do have one now. */ - if (notify_frontend && !cbhead) + * we do have one now. + * + * If cbcurr is non-NULL, i.e. we are actually in the middle of + * executing a callback right now, then we count that as the queue + * already having been non-empty. That saves the front end getting + * a constant stream of needless re-notifications if the last + * callback keeps re-scheduling itself. + */ + if (notify_frontend && !cbhead && !cbcurr) notify_frontend(frontend); if (cbtail) @@ -51,24 +83,27 @@ void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) void run_toplevel_callbacks(void) { if (cbhead) { - struct callback *cb = cbhead; /* - * Careful ordering here. We call the function _before_ - * advancing cbhead (though, of course, we must free cb - * _after_ advancing it). This means that if the very last - * callback schedules another callback, cbhead does not become - * NULL at any point, and so the frontend notification - * function won't be needlessly pestered. + * Transfer the head callback into cbcurr to indicate that + * it's being executed. Then operations which transform the + * queue, like delete_callbacks_for_context, can proceed as if + * it's not there. */ - cb->fn(cb->ctx); - cbhead = cb->next; - sfree(cb); + cbcurr = cbhead; + cbhead = cbhead->next; if (!cbhead) cbtail = NULL; + + /* + * Now run the callback, and then clear it out of cbcurr. + */ + cbcurr->fn(cbcurr->ctx); + sfree(cbcurr); + cbcurr = NULL; } } int toplevel_callback_pending(void) { - return cbhead != NULL; + return cbcurr != NULL || cbhead != NULL; } diff --git a/charset/utf8.c b/charset/utf8.c index 3c777292d..fe46cf988 100644 --- a/charset/utf8.c +++ b/charset/utf8.c @@ -267,7 +267,7 @@ void utf8_read_test(int line, char *input, int inlen, ...) } if (l != str[i]) { printf("%d: char %d came out as %08x, should be %08x\n", - line, i, str[i], l); + line, i, str[i], (unsigned)l); total_errs++; } } @@ -306,7 +306,7 @@ void utf8_write_test(int line, const long *input, int inlen, ...) } if (l != str[i]) { printf("%d: char %d came out as %08x, should be %08x\n", - line, i, str[i], l); + line, i, str[i], (unsigned)l); total_errs++; } } diff --git a/cmdline.c b/cmdline.c index f288ed629..1a14cc997 100644 --- a/cmdline.c +++ b/cmdline.c @@ -159,11 +159,242 @@ static int cmdline_check_unavailable(int flag, const char *p) if (need_save < 0) return x; \ } while (0) +static int seen_hostname_argument = FALSE; +static int seen_port_argument = FALSE; + int cmdline_process_param(const char *p, char *value, int need_save, Conf *conf) { int ret = 0; + if (p[0] != '-') { + if (need_save < 0) + return 0; + + /* + * Common handling for the tools whose initial command-line + * arguments specify a hostname to connect to, i.e. PuTTY and + * Plink. Doesn't count the file transfer tools, because their + * hostname specification appears as part of a more + * complicated scheme. + */ + + if ((cmdline_tooltype & TOOLTYPE_HOST_ARG) && + !seen_hostname_argument && + (!(cmdline_tooltype & TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD) || + !loaded_session || !conf_launchable(conf))) { + /* + * Treat this argument as a host name, if we have not yet + * seen a host name argument or -load. + * + * Exception, in some tools (Plink): if we have seen -load + * but it didn't create a launchable session, then we + * still accept a hostname argument following that -load. + * This allows you to make saved sessions that configure + * lots of other stuff (colour schemes, terminal settings + * etc) and then say 'putty -load sessionname hostname'. + * + * Also, we carefully _don't_ test conf for launchability + * if we haven't been explicitly told to load a session + * (otherwise saving a host name into Default Settings + * would cause 'putty' on its own to immediately launch + * the default session and never be able to do anything + * else). + */ + if (!strncmp(p, "telnet:", 7)) { + /* + * If the argument starts with "telnet:", set the + * protocol to Telnet and process the string as a + * Telnet URL. + */ + + /* + * Skip the "telnet:" or "telnet://" prefix. + */ + p += 7; + if (p[0] == '/' && p[1] == '/') + p += 2; + conf_set_int(conf, CONF_protocol, PROT_TELNET); + + /* + * The next thing we expect is a host name. + */ + { + const char *host = p; + char *buf; + + p += host_strcspn(p, ":/"); + buf = dupprintf("%.*s", (int)(p - host), host); + conf_set_str(conf, CONF_host, buf); + sfree(buf); + seen_hostname_argument = TRUE; + } + + /* + * If the host name is followed by a colon, then + * expect a port number after it. + */ + if (*p == ':') { + p++; + + conf_set_int(conf, CONF_port, atoi(p)); + /* + * Set the flag that will stop us from treating + * the next argument as a separate port; this one + * counts as explicitly provided. + */ + seen_port_argument = TRUE; + } else { + conf_set_int(conf, CONF_port, -1); + } + } else { + char *user = NULL, *hostname = NULL; + const char *hostname_after_user; + int port_override = -1; + size_t len; + + /* + * Otherwise, treat it as a bare host name. + */ + + if (cmdline_tooltype & TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX) { + /* + * Here Plink checks for a comma-separated + * protocol prefix, e.g. 'ssh,hostname' or + * 'ssh,user@hostname'. + * + * I'm not entirely sure why; this behaviour dates + * from 2000 and isn't explained. But I _think_ it + * has to do with CVS transport or similar use + * cases, in which the end user invokes the SSH + * client indirectly, via some means that only + * lets them pass a single string argument, and it + * was occasionally useful to shoehorn the choice + * of protocol into that argument. + */ + const char *comma = strchr(p, ','); + if (comma) { + char *prefix = dupprintf("%.*s", (int)(comma - p), p); + const Backend *b = backend_from_name(prefix); + + if (b) { + default_protocol = b->protocol; + conf_set_int(conf, CONF_protocol, + default_protocol); + port_override = b->default_port; + } else { + cmdline_error("unrecognised protocol prefix '%s'", + prefix); + } + + sfree(prefix); + p = comma + 1; + } + } + + hostname_after_user = p; + if (cmdline_tooltype & TOOLTYPE_HOST_ARG_CAN_BE_SESSION) { + /* + * If the hostname argument can also be a saved + * session (see below), then here we also check + * for a user@ prefix, which will override the + * username from the saved session. + * + * (If the hostname argument _isn't_ a saved + * session, we don't do this.) + */ + const char *at = strrchr(p, '@'); + if (at) { + user = dupprintf("%.*s", (int)(at - p), p); + hostname_after_user = at + 1; + } + } + + /* + * Write the whole hostname argument (minus only that + * optional protocol prefix) into the existing Conf, + * for tools that don't treat it as a saved session + * and as a fallback for those that do. + */ + hostname = dupstr(p + strspn(p, " \t")); + len = strlen(hostname); + while (len > 0 && (hostname[len-1] == ' ' || + hostname[len-1] == '\t')) + hostname[--len] = '\0'; + seen_hostname_argument = TRUE; + conf_set_str(conf, CONF_host, hostname); + + if ((cmdline_tooltype & TOOLTYPE_HOST_ARG_CAN_BE_SESSION) && + !loaded_session) { + /* + * For some tools, we equivocate between a + * hostname argument and an argument naming a + * saved session. Here we attempt to load a + * session with the specified name, and if that + * succeeds, we overwrite the entire Conf with it. + * + * We skip this check if a -load option has + * already happened, so that + * + * plink -load non-launchable-session hostname + * + * will treat 'hostname' as a hostname _even_ if a + * saved session called 'hostname' exists. (This + * doesn't lose any functionality someone could + * have needed, because if 'hostname' did cause a + * session to be loaded, then it would overwrite + * everything from the previously loaded session. + * So if that was the behaviour someone wanted, + * then they could get it by leaving off the + * -load completely.) + */ + Conf *conf2 = conf_new(); + do_defaults(hostname_after_user, conf2); + if (conf_launchable(conf2)) { + conf_copy_into(conf, conf2); + loaded_session = TRUE; + /* And override the username if one was given. */ + if (user) + conf_set_str(conf, CONF_username, user); + } + conf_free(conf2); + } + + sfree(hostname); + sfree(user); + + if (port_override >= 0) + conf_set_int(conf, CONF_port, port_override); + } + + return 1; + } else if ((cmdline_tooltype & TOOLTYPE_PORT_ARG) && + !seen_port_argument) { + /* + * If we've already got a host name from the command line + * (either as a hostname argument or a qualifying -load), + * but not a port number, then treat the next argument as + * a port number. + * + * We handle this by calling ourself recursively to + * pretend we received a -P argument, so that it will be + * deferred until it's a good moment to run it. + */ + char *dup = dupstr(p); /* 'value' is not a const char * */ + int retd = cmdline_process_param("-P", dup, 1, conf); + sfree(dup); + assert(retd == 2); + seen_port_argument = TRUE; + return 1; + } else { + /* + * Refuse to recognise this argument, and give it back to + * the tool's own command-line processing. + */ + return 0; + } + } + if (!strcmp(p, "-load")) { RETURN(2); /* This parameter must be processed immediately rather than being @@ -403,7 +634,18 @@ int cmdline_process_param(const char *p, char *value, SAVEABLE(0); conf_set_int(conf, CONF_tryagent, FALSE); } - + if (!strcmp(p, "-share")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_int(conf, CONF_ssh_connection_sharing, TRUE); + } + if (!strcmp(p, "-noshare")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_int(conf, CONF_ssh_connection_sharing, FALSE); + } if (!strcmp(p, "-A")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); @@ -637,3 +879,35 @@ void cmdline_run_saved(Conf *conf) saves[pri].nsaved = 0; } } + +int cmdline_host_ok(Conf *conf) +{ + /* + * Return TRUE if the command-line arguments we've processed in + * TOOLTYPE_HOST_ARG mode are sufficient to justify launching a + * session. + */ + assert(cmdline_tooltype & TOOLTYPE_HOST_ARG); + + /* + * Of course, if we _can't_ launch a session, the answer is + * clearly no. + */ + if (!conf_launchable(conf)) + return FALSE; + + /* + * But also, if we haven't seen either a -load option or a + * hostname argument, i.e. the only saved settings we've loaded + * are Default Settings plus any non-hostname-based stuff from the + * command line, then the answer is still no, _even_ if this Conf + * is launchable. Otherwise, if you saved your favourite hostname + * into Default Settings, then just running 'putty' without + * arguments would connect to it without ever offering you the + * option to connect to something else or change the setting. + */ + if (!seen_hostname_argument && !loaded_session) + return FALSE; + + return TRUE; +} diff --git a/config.c b/config.c index 220c1aa8f..a12a6d646 100644 --- a/config.c +++ b/config.c @@ -1005,7 +1005,7 @@ static void ttymodes_handler(union control *ctrl, void *dlg, char type; { - const char *types = "ANV"; + const char types[] = {'A', 'N', 'V'}; int button = dlg_radiobutton_get(td->valradio, dlg); assert(button >= 0 && button < lenof(types)); type = types[button]; @@ -1337,6 +1337,105 @@ static void manual_hostkey_handler(union control *ctrl, void *dlg, } } +static void clipboard_selector_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + int setting = ctrl->generic.context.i; +#ifdef NAMED_CLIPBOARDS + int strsetting = ctrl->editbox.context2.i; +#endif + + static const struct { + const char *name; + int id; + } options[] = { + {"No action", CLIPUI_NONE}, + {CLIPNAME_IMPLICIT, CLIPUI_IMPLICIT}, + {CLIPNAME_EXPLICIT, CLIPUI_EXPLICIT}, + }; + + if (event == EVENT_REFRESH) { + int i, val = conf_get_int(conf, setting); + + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + +#ifdef NAMED_CLIPBOARDS + for (i = 0; i < lenof(options); i++) + dlg_listbox_add(ctrl, dlg, options[i].name); + if (val == CLIPUI_CUSTOM) { + const char *sval = conf_get_str(conf, strsetting); + for (i = 0; i < lenof(options); i++) + if (!strcmp(sval, options[i].name)) + break; /* needs escaping */ + if (i < lenof(options) || sval[0] == '=') { + char *escaped = dupcat("=", sval, (const char *)NULL); + dlg_editbox_set(ctrl, dlg, escaped); + sfree(escaped); + } else { + dlg_editbox_set(ctrl, dlg, sval); + } + } else { + dlg_editbox_set(ctrl, dlg, options[0].name); /* fallback */ + for (i = 0; i < lenof(options); i++) + if (val == options[i].id) + dlg_editbox_set(ctrl, dlg, options[i].name); + } +#else + for (i = 0; i < lenof(options); i++) + dlg_listbox_addwithid(ctrl, dlg, options[i].name, options[i].id); + dlg_listbox_select(ctrl, dlg, 0); /* fallback */ + for (i = 0; i < lenof(options); i++) + if (val == options[i].id) + dlg_listbox_select(ctrl, dlg, i); +#endif + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_SELCHANGE +#ifdef NAMED_CLIPBOARDS + || event == EVENT_VALCHANGE +#endif + ) { +#ifdef NAMED_CLIPBOARDS + const char *sval = dlg_editbox_get(ctrl, dlg); + int i; + + for (i = 0; i < lenof(options); i++) + if (!strcmp(sval, options[i].name)) { + conf_set_int(conf, setting, options[i].id); + conf_set_str(conf, strsetting, ""); + break; + } + if (i == lenof(options)) { + conf_set_int(conf, setting, CLIPUI_CUSTOM); + if (sval[0] == '=') + sval++; + conf_set_str(conf, strsetting, sval); + } +#else + int index = dlg_listbox_index(ctrl, dlg); + if (index >= 0) { + int val = dlg_listbox_getid(ctrl, dlg, index); + conf_set_int(conf, setting, val); + } +#endif + } +} + +static void clipboard_control(struct controlset *s, const char *label, + char shortcut, int percentage, intorptr helpctx, + int setting, int strsetting) +{ +#ifdef NAMED_CLIPBOARDS + ctrl_combobox(s, label, shortcut, percentage, helpctx, + clipboard_selector_handler, I(setting), I(strsetting)); +#else + /* strsetting isn't needed in this case */ + ctrl_droplist(s, label, shortcut, percentage, helpctx, + clipboard_selector_handler, I(setting)); +#endif +} + void setup_config_box(struct controlbox *b, int midsession, int protocol, int protcfginfo) { @@ -1860,8 +1959,30 @@ void setup_config_box(struct controlbox *b, int midsession, "Normal", 'n', I(0), "Rectangular block", 'r', I(1), NULL); - s = ctrl_getset(b, "Window/Selection", "charclass", - "Control the select-one-word-at-a-time mode"); + s = ctrl_getset(b, "Window/Selection", "clipboards", + "Assign copy/paste actions to clipboards"); + ctrl_checkbox(s, "Auto-copy selected text to " + CLIPNAME_EXPLICIT_OBJECT, + NO_SHORTCUT, HELPCTX(selection_autocopy), + conf_checkbox_handler, I(CONF_mouseautocopy)); + clipboard_control(s, "Mouse paste action:", NO_SHORTCUT, 60, + HELPCTX(selection_clipactions), + CONF_mousepaste, CONF_mousepaste_custom); + clipboard_control(s, "{Ctrl,Shift} + Ins:", NO_SHORTCUT, 60, + HELPCTX(selection_clipactions), + CONF_ctrlshiftins, CONF_ctrlshiftins_custom); + clipboard_control(s, "Ctrl + Shift + {C,V}:", NO_SHORTCUT, 60, + HELPCTX(selection_clipactions), + CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); + + /* + * The Window/Selection/Words panel. + */ + ctrl_settitle(b, "Window/Selection/Words", + "Options controlling word-by-word selection"); + + s = ctrl_getset(b, "Window/Selection/Words", "charclass", + "Classes of character that group together"); ccd = (struct charclass_data *) ctrl_alloc(b, sizeof(struct charclass_data)); ccd->listbox = ctrl_listbox(s, "Character classes:", 'e', @@ -1898,6 +2019,9 @@ void setup_config_box(struct controlbox *b, int midsession, ctrl_checkbox(s, "Allow terminal to use xterm 256-colour mode", '2', HELPCTX(colours_xterm256), conf_checkbox_handler, I(CONF_xterm_256_colour)); + ctrl_checkbox(s, "Allow terminal to use 24-bit colours", '4', + HELPCTX(colours_truecolour), conf_checkbox_handler, + I(CONF_true_colour)); ctrl_radiobuttons(s, "Indicate bolded text by changing:", 'b', 3, HELPCTX(colours_bold), conf_radiobutton_handler, I(CONF_bold_style), diff --git a/contrib/cygtermd/README b/contrib/cygtermd/README index ebfdfdd77..a722fe10a 100644 --- a/contrib/cygtermd/README +++ b/contrib/cygtermd/README @@ -8,4 +8,4 @@ install it in Cygwin's /bin, and configure PuTTY to use it as a local proxy process. For detailed instructions, see the PuTTY Wishlist page at -http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/cygwin-terminal-window.html +https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/cygwin-terminal-window.html diff --git a/doc/Makefile b/doc/Makefile index e7bf287e0..cb079fb5d 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -44,19 +44,17 @@ INPUTS = $(patsubst %,%.but,$(CHAPTERS)) HALIBUT = halibut index.html: $(INPUTS) - $(HALIBUT) --text --html --winhelp $(INPUTS) + $(HALIBUT) --text --html --winhelp --chm $(INPUTS) -# During formal builds it's useful to be able to build this one alone. +# During formal builds it's useful to be able to build these ones alone. putty.hlp: $(INPUTS) $(HALIBUT) --winhelp $(INPUTS) +putty.chm: $(INPUTS) + $(HALIBUT) --chm $(INPUTS) putty.info: $(INPUTS) $(HALIBUT) --info $(INPUTS) -chm: putty.hhp -putty.hhp: $(INPUTS) chm.but - $(HALIBUT) --html $(INPUTS) chm.but - MKMAN = $(HALIBUT) --man=$@ mancfg.but $< MANPAGES = putty.1 puttygen.1 plink.1 pscp.1 psftp.1 puttytel.1 pterm.1 \ pageant.1 diff --git a/doc/blurb.but b/doc/blurb.but index 227300f46..e5e03a600 100644 --- a/doc/blurb.but +++ b/doc/blurb.but @@ -7,17 +7,23 @@ \cfg{xhtml-leaf-contains-contents}{true} \cfg{xhtml-body-end}{

If you want to provide feedback on this manual or on the PuTTY tools themselves, see the -Feedback +Feedback page.

} \cfg{html-template-fragment}{%k}{%b} \cfg{info-max-file-size}{0} +\cfg{chm-contents-filename}{index.html} +\cfg{chm-template-filename}{%k.html} +\cfg{chm-head-end}{} +\cfg{chm-extra-file}{chm.css} + \cfg{xhtml-contents-filename}{index.html} \cfg{text-filename}{puttydoc.txt} \cfg{winhelp-filename}{putty.hlp} \cfg{info-filename}{putty.info} +\cfg{chm-filename}{putty.chm} PuTTY is a free (MIT-licensed) Windows Telnet and SSH client. This manual documents PuTTY, and its companion utilities PSCP, PSFTP, diff --git a/doc/chm.but b/doc/chm.but deleted file mode 100644 index 44d1dca3f..000000000 --- a/doc/chm.but +++ /dev/null @@ -1,22 +0,0 @@ -\# File containing the magic HTML configuration directives to create -\# an MS HTML Help project. We put this on the end of the PuTTY -\# docs build command line to build the HHP and friends. - -\cfg{html-leaf-level}{infinite} -\cfg{html-leaf-contains-contents}{false} -\cfg{html-suppress-navlinks}{true} -\cfg{html-suppress-address}{true} - -\cfg{html-contents-filename}{index.html} -\cfg{html-template-filename}{%k.html} -\cfg{html-template-fragment}{%k} - -\cfg{html-mshtmlhelp-chm}{putty.chm} -\cfg{html-mshtmlhelp-project}{putty.hhp} -\cfg{html-mshtmlhelp-contents}{putty.hhc} -\cfg{html-mshtmlhelp-index}{putty.hhk} - -\cfg{html-body-end}{} - -\cfg{html-head-end}{} - diff --git a/doc/config.but b/doc/config.but index eb96fc17e..790365827 100644 --- a/doc/config.but +++ b/doc/config.but @@ -499,8 +499,8 @@ instead of relying on the automatic detection. \cfg{winhelp-topic}{terminal.printing} A lot of VT100-compatible terminals support printing under control -of the remote server. PuTTY supports this feature as well, but it is -turned off by default. +of the remote server (sometimes called \q{passthrough printing}). +PuTTY supports this feature as well, but it is turned off by default. To enable remote-controlled printing, choose a printer from the \q{Printer to send ANSI printer output to} drop-down list box. This @@ -1469,13 +1469,87 @@ select a rectangular block. Using the \q{Default selection mode} control, you can set \i{rectangular selection} as the default, and then you have to hold down Alt to get the \e{normal} behaviour. -\S{config-charclasses} Configuring \i{word-by-word selection} +\S{config-clipboards} Assigning copy and paste actions to clipboards -\cfg{winhelp-topic}{selection.charclasses} +Here you can configure which clipboard(s) are written or read by +PuTTY's various copy and paste actions. + +The X Window System provides multiple clipboards (or \q{selections}), +and many applications support more than one of them by a different +user interface mechanism. + +The two most commonly used selections are called \cq{PRIMARY} and +\cq{CLIPBOARD}; in applications supporting both, the usual behaviour +is that \cw{PRIMARY} is used by mouse-only actions (selecting text +automatically copies it to \cw{PRIMARY}, and middle-clicking pastes +from \cw{PRIMARY}), whereas \cw{CLIPBOARD} is used by explicit Copy +and Paste menu items or keypresses such as Ctrl-C and Ctrl-V. + +On other platforms, where there is a single system clipboard, PuTTY +provides a second clipboard-like facility by permitting you to paste +the text you last selected in \e{this window}, whether or not it is +currently also in the system clipboard. + +\S2{config-selection-autocopy} \q{Auto-copy selected text} + +\cfg{winhelp-topic}{selection.autocopy} + +The checkbox \q{Auto-copy selected text to system clipboard} controls +whether or not selecting text in the PuTTY terminal window +automatically has the side effect of copying it to the system +clipboard, without requiring a separate user interface action. + +On X, the wording of this option is changed slightly so that +\cq{CLIPBOARD} is mentioned in place of the \q{system clipboard}. Text +selected in the terminal window will \e{always} be automatically +placed in the \cw{PRIMARY} selection, but if you tick this box, it +will \e{also} be placed in \cq{CLIPBOARD} at the same time. + +\S2{config-selection-clipactions} Choosing a clipboard for UI actions + +\cfg{winhelp-topic}{selection.clipactions} + +PuTTY has three user-interface actions which can be configured to +paste into the terminal (not counting menu items). You can click +whichever mouse button (if any) is configured to paste (see +\k{config-mouse}); you can press Shift-Ins; or you can press +Ctrl-Shift-V, although that action is not enabled by default. + +You can configure which of the available clipboards each of these +actions pastes from (including turning the paste action off +completely). On platforms with a single system clipboard, the +available options are to paste from that clipboard or to paste from +PuTTY's internal memory of the last selected text within that window. +On X, the standard options are \cw{CLIPBOARD} or \cw{PRIMARY}. -PuTTY will select a word at a time in the terminal window if you -\i{double-click} to begin the drag. This panel allows you to control -precisely what is considered to be a word. +(\cw{PRIMARY} is conceptually similar in that it \e{also} refers to +the last selected text \dash just across all applications instead of +just this window.) + +The two keyboard options each come with a corresponding key to copy +\e{to} the same clipboard. Whatever you configure Shift-Ins to paste +from, Ctrl-Ins will copy to the same location; similarly, Ctrl-Shift-C +will copy to whatever Ctrl-Shift-V pastes from. + +On X, you can also enter a selection name of your choice. For example, +there is a rarely-used standard selection called \cq{SECONDARY}, which +Emacs (for example) can work with if you hold down the Meta key while +dragging to select or clicking to paste; if you configure a PuTTY +keyboard action to access this clipboard, then you can interoperate +with other applications' use of it. Another thing you could do would +be to invent a clipboard name yourself, to create a special clipboard +shared \e{only} between instances of PuTTY, or between just instances +configured in that particular way. + +\H{config-selection-words} The Words panel + +PuTTY will \I{word-by-word selection}select a word at a time in the +terminal window if you \i{double-click} to begin the drag. This panel +allows you to control precisely what is considered to be a word. + +\S{config-charclasses} Character classes + +\cfg{winhelp-topic}{selection.charclasses} Each character is given a \e{class}, which is a small number (typically 0, 1 or 2). PuTTY considers a single word to be any @@ -1549,6 +1623,15 @@ If you do not see \cq{colors#256} in the output, you may need to change your terminal setting. On modern Linux machines, you could try \cq{xterm-256color}. +\S{config-truecolour} \q{Allow terminal to use 24-bit colour} + +\cfg{winhelp-topic}{colours.truecolour} + +This option is enabled by default. If it is disabled, PuTTY will +ignore any control sequences sent by the server which use the control +sequences supported by modern terminals to specify arbitrary 24-bit +RGB colour value. + \S{config-boldcolour} \q{Indicate bolded text by changing...} \cfg{winhelp-topic}{colours.bold} @@ -2507,7 +2590,7 @@ used: Disabling data-based rekeys entirely is a bad idea. The \i{integrity}, and to a lesser extent, \i{confidentiality} of the SSH-2 protocol depend -in part on rekeys occuring before a 32-bit packet sequence number +in part on rekeys occurring before a 32-bit packet sequence number wraps around. Unlike time-based rekeys, data-based rekeys won't occur when the SSH connection is idle, so they shouldn't cause the same problems. The SSH-1 protocol, incidentally, has even weaker integrity @@ -2959,7 +3042,7 @@ modes from the local terminal, if any. } -\b If \q{Nothing} is selected, no value for the mode will not be +\b If \q{Nothing} is selected, no value for the mode will be specified to the server under any circumstances. \b If a value is specified, it will be sent to the server under all @@ -3006,18 +3089,19 @@ PuTTY in a variety of ways, such as \cw{true}/\cw{false}, \cw{no} is different from not sending the mode at all.) \b The boolean mode \I{IUTF8 terminal mode}\cw{IUTF8} signals to the -server whether the terminal character set is \i{UTF-8} or not. -If this is set incorrectly, keys like backspace may do the wrong thing -in some circumstances. However, setting this is not usually -sufficient to cause servers to expect the terminal to be in UTF-8 mode; -POSIX servers will generally require the locale to be set (by some -server-dependent means), although many default to UTF-8. Also, -since this mode was added to the SSH protocol much later than the -others, \#{circa 2016} many servers (particularly older servers) do -not honour this mode sent over SSH; indeed, a few poorly-written -servers object to its mere presence, so you may find you need to set -it to not be sent at all. When set to \q{Auto}, this follows the local -configured character set (see \k{config-charset}). +server whether the terminal character set is \i{UTF-8} or not, for +purposes such as basic line editing; if this is set incorrectly, +the backspace key may erase the wrong amount of text, for instance. +However, simply setting this is not usually sufficient for the server +to use UTF-8; POSIX servers will generally also require the locale to +be set (by some server-dependent means), although many newer +installations default to UTF-8. Also, since this mode was added to the +SSH protocol much later than the others, \#{circa 2016} many servers +(particularly older servers) do not honour this mode sent over SSH; +indeed, a few poorly-written servers object to its mere presence, so +you may find you need to set it to not be sent at all. When set to +\q{Auto}, this follows the local configured character set (see +\k{config-charset}). \b Terminal speeds are configured elsewhere; see \k{config-termspeed}. diff --git a/doc/errors.but b/doc/errors.but index fdbdd861a..8e353fb9b 100644 --- a/doc/errors.but +++ b/doc/errors.but @@ -122,9 +122,7 @@ ridiculous amount of memory, and will terminate with an \q{Out of memory} error. This can happen in SSH-2, if PuTTY and the server have not enabled -encryption in the same way (see \k{faq-outofmem} in the FAQ). Some -versions of \i{OpenSSH} have a known problem with this: see -\k{faq-openssh-bad-openssl}. +encryption in the same way (see \k{faq-outofmem} in the FAQ). This can also happen in PSCP or PSFTP, if your \i{login scripts} on the server generate output: the client program will be expecting an SFTP @@ -233,8 +231,13 @@ protection (such as HTTP) will manifest in more subtle failures (such as misdisplayed text or images in a web browser) which may not be noticed. -A known server problem which can cause this error is described in -\k{faq-openssh-bad-openssl} in the FAQ. +Occasionally this has been caused by server bugs. An example is the +bug described at \k{config-ssh-bug-hmac2}, although you're very +unlikely to encounter that one these days. + +In this context MAC stands for \ii{Message Authentication Code}. It's a +cryptographic term, and it has nothing at all to do with Ethernet +MAC (Media Access Control) addresses, or with the Apple computer. \H{errors-garbled} \q{Incoming packet was garbled on decryption} @@ -247,10 +250,7 @@ in the server, or in between. If you get this error, one thing you could try would be to fiddle with the setting of \q{Miscomputes SSH-2 encryption keys} (see \k{config-ssh-bug-derivekey2}) or \q{Ignores SSH-2 maximum packet -size} (see \k{config-ssh-bug-maxpkt2}) on the Bugs panel . - -Another known server problem which can cause this error is described -in \k{faq-openssh-bad-openssl} in the FAQ. +size} (see \k{config-ssh-bug-maxpkt2}) on the Bugs panel. \H{errors-x11-proxy} \q{PuTTY X11 proxy: \e{various errors}} diff --git a/doc/faq.but b/doc/faq.but index 42f965b27..4d9e14a93 100644 --- a/doc/faq.but +++ b/doc/faq.but @@ -27,18 +27,18 @@ else. \I{supported features}In general, if you want to know if PuTTY supports a particular feature, you should look for it on the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}{PuTTY web site}. +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}{PuTTY web site}. In particular: \b try the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{changes +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{changes page}, and see if you can find the feature on there. If a feature is listed there, it's been implemented. If it's listed as a change made \e{since} the latest version, it should be available in the development snapshots, in which case testing will be very welcome. \b try the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist page}, and see if you can find the feature there. If it's on there, and not in the \q{Recently fixed} section, it probably \e{hasn't} been implemented. @@ -54,7 +54,7 @@ version 0.52. \cw{ssh.com} SSH-2 private key files? PuTTY doesn't support this natively (see -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/key-formats-natively.html}{the wishlist entry} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/key-formats-natively.html}{the wishlist entry} for reasons why not), but as of 0.53 PuTTYgen can convert both OpenSSH and \cw{ssh.com} private key files into PuTTY's format. @@ -236,7 +236,7 @@ port, or any other port of PuTTY, they were mistaken. We don't. There are some third-party ports to various platforms, mentioned on the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website}. +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website}. \S{faq-unix}{Question} \I{Unix version}Is there a port to Unix? @@ -323,7 +323,7 @@ for, it might be a long time before any of us get round to learning a new system and doing the port for that. However, some of the work has been done by other people; see the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website} for various third-party ports. \S{faq-iphone}{Question} Will there be a port to the iPhone? @@ -351,7 +351,7 @@ Most of the code cleanup work would be a good thing to happen in general, so if anyone feels like helping, we wouldn't say no. See also -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/dll-frontend.html}{the wishlist entry}. +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/dll-frontend.html}{the wishlist entry}. \S{faq-vb}{Question} Is the SSH or Telnet code available as a Visual Basic component? @@ -601,34 +601,6 @@ documentation.) \H{faq-trouble} Troubleshooting -\S{faq-incorrect-mac}{Question} Why do I see \q{Incorrect MAC -received on packet}? - -One possible cause of this that used to be common is a bug in old -SSH-2 servers distributed by \cw{ssh.com}. (This is not the only -possible cause; see \k{errors-crc} in the documentation.) -Version 2.3.0 and below of their SSH-2 server -constructs Message Authentication Codes in the wrong way, and -expects the client to construct them in the same wrong way. PuTTY -constructs the MACs correctly by default, and hence these old -servers will fail to work with it. - -If you are using PuTTY version 0.52 or better, this should work -automatically: PuTTY should detect the buggy servers from their -version number announcement, and automatically start to construct -its MACs in the same incorrect manner as they do, so it will be able -to work with them. - -If you are using PuTTY version 0.51 or below, you can enable the -workaround by going to the SSH panel and ticking the box labelled -\q{Imitate SSH2 MAC bug}. It's possible that you might have to do -this with 0.52 as well, if a buggy server exists that PuTTY doesn't -know about. - -In this context MAC stands for \ii{Message Authentication Code}. It's a -cryptographic term, and it has nothing at all to do with Ethernet -MAC (Media Access Control) addresses. - \S{faq-pscp-protocol}{Question} Why do I see \q{Fatal: Protocol error: Expected control record} in PSCP? @@ -664,21 +636,6 @@ Clicking on \q{ANSI Green} won't turn your session green; it will only allow you to adjust the \e{shade} of green used when PuTTY is instructed by the server to display green text. -\S{faq-winsock2}{Question} Plink on \i{Windows 95} says it can't find -\i\cw{WS2_32.DLL}. - -Plink requires the extended Windows network library, WinSock version -2. This is installed as standard on Windows 98 and above, and on -Windows NT, and even on later versions of Windows 95; but early -Win95 installations don't have it. - -In order to use Plink on these systems, you will need to download -the -\W{http://www.microsoft.com/windows95/downloads/contents/wuadmintools/s_wunetworkingtools/w95sockets2/}{WinSock 2 upgrade}: - -\c http://www.microsoft.com/windows95/downloads/contents/ -\c wuadmintools/s_wunetworkingtools/w95sockets2/ - \S{faq-outofmem}{Question} After trying to establish an SSH-2 connection, PuTTY says \q{\ii{Out of memory}} and dies. @@ -891,45 +848,10 @@ us \q{I wanted the F1 key to send \c{^[[11~}, but instead it's sending \c{^[OP}, can this be done?}, or something similar. You should still read the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/feedback.html}{Feedback +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/feedback.html}{Feedback page} on the PuTTY website (also provided as \k{feedback} in the manual), and follow the guidelines contained in that. -\S{faq-openssh-bad-openssl}{Question} Since my SSH server was upgraded -to \i{OpenSSH} 3.1p1/3.4p1, I can no longer connect with PuTTY. - -There is a known problem when OpenSSH has been built against an -incorrect version of OpenSSL; the quick workaround is to configure -PuTTY to use SSH protocol 2 and the Blowfish cipher. - -For more details and OpenSSH patches, see -\W{http://bugzilla.mindrot.org/show_bug.cgi?id=138}{bug 138} in the -OpenSSH BTS. - -This is not a PuTTY-specific problem; if you try to connect with -another client you'll likely have similar problems. (Although PuTTY's -default cipher differs from many other clients.) - -\e{OpenSSH 3.1p1:} configurations known to be broken (and symptoms): - -\b SSH-2 with AES cipher (PuTTY says \q{Assertion failed! Expression: -(len & 15) == 0} in \cw{sshaes.c}, or \q{Out of memory}, or crashes) - -\b SSH-2 with 3DES (PuTTY says \q{Incorrect MAC received on packet}) - -\b SSH-1 with Blowfish (PuTTY says \q{Incorrect CRC received on -packet}) - -\b SSH-1 with 3DES - -\e{OpenSSH 3.4p1:} as of 3.4p1, only the problem with SSH-1 and -Blowfish remains. Rebuild your server, apply the patch linked to from -bug 138 above, or use another cipher (e.g., 3DES) instead. - -\e{Other versions:} we occasionally get reports of the same symptom -and workarounds with older versions of OpenSSH, although it's not -clear the underlying cause is the same. - \S{faq-ssh2key-ssh1conn}{Question} Why do I see \q{Couldn't load private key from ...}? Why can PuTTYgen load my key but not PuTTY? @@ -1060,7 +982,7 @@ still. We do not recommend it.) This is caused by a bug in certain versions of \i{Windows XP} which is triggered by PuTTY 0.58. This was fixed in 0.59. The -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/xp-wont-run}{\q{xp-wont-run}} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/xp-wont-run}{\q{xp-wont-run}} entry in PuTTY's wishlist has more details. \S{faq-system32}{Question} When I put 32-bit PuTTY in @@ -1080,6 +1002,26 @@ appropriate kind of binaries in \cw{SYSTEM32}. Thus, operations in the PuTTY suite that involve it accessing its own executables, such as \i{\q{New Session}} and \q{Duplicate Session}, will not work. +\S{faq-iutf8}{Question} After I upgraded PuTTY to 0.68, I can no longer +connect to my embedded device or appliance. + +If your SSH server has started unexpectedly closing SSH connections +after you enter your password, and it worked before 0.68, you may have +a buggy server that objects to certain SSH protocol extensions. + +The SSH protocol recently gained a new \q{terminal mode}, \cw{IUTF8}, +which PuTTY sends by default; see \k{config-ttymodes}. This is the +first new terminal mode since the SSH-2 protocol was defined. While +servers are supposed to ignore modes they don't know about, some buggy +servers will unceremoniously close the connection if they see anything +they don't recognise. SSH servers in embedded devices, network +appliances, and the like seem to disproportionately have this bug. + +If you think you have such a server, from 0.69 onwards you can disable +sending of the \cw{IUTF8} mode: on the SSH / TTY panel, select +\cw{IUTF8} on the list, select \q{Nothing}, and press \q{Set}. (It's +not possible to disable sending this mode in 0.68.) + \H{faq-secure} Security questions \S{faq-publicpc}{Question} Is it safe for me to download PuTTY and diff --git a/doc/feedback.but b/doc/feedback.but index e0854fc54..b8428e415 100644 --- a/doc/feedback.but +++ b/doc/feedback.but @@ -112,7 +112,7 @@ If you think you have found a bug in PuTTY, your first steps should be: \b Check the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist page} on the PuTTY website, and see if we already know about the problem. If we do, it is almost certainly not necessary to mail us about it, unless you think you have extra information that might be @@ -121,12 +121,12 @@ specific extra information about a particular bug, the Wishlist page will say so.) \b Check the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change Log} on the PuTTY website, and see if we have already fixed the bug in the \i{development snapshots}. \b Check the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/faq.html}{FAQ} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/faq.html}{FAQ} on the PuTTY website (also provided as \k{faq} in the manual), and see if it answers your question. The FAQ lists the most common things which people think are bugs, but which aren't bugs. @@ -188,7 +188,7 @@ you haven't supplied us with full information about the actual bug, then we won't be able to find a better solution. \b -\W{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html}\cw{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html} +\W{https://www.chiark.greenend.org.uk/~sgtatham/bugs.html}\cw{https://www.chiark.greenend.org.uk/~sgtatham/bugs.html} is an article on how to report bugs effectively in general. If your bug report is \e{particularly} unclear, we may ask you to go away, read this article, and then report the bug again. @@ -224,14 +224,14 @@ If you want to request a new feature in PuTTY, the very first things you should do are: \b Check the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist page} on the PuTTY website, and see if your feature is already on the list. If it is, it probably won't achieve very much to repeat the request. (But see \k{feedback-feature-priority} if you want to persuade us to give your particular feature higher priority.) \b Check the Wishlist and -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change Log} on the PuTTY website, and see if we have already added your feature in the development snapshots. If it isn't clear, download the latest development snapshot and see if the feature is present. @@ -350,7 +350,7 @@ Of course, if the web site has some other error (Connection Refused, If you want to report a problem with our web site, check that you're looking at our \e{real} web site and not a mirror. The real web site is at -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\c{http://www.chiark.greenend.org.uk/~sgtatham/putty/}; +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\c{https://www.chiark.greenend.org.uk/~sgtatham/putty/}; if that's not where you're reading this, then don't report the problem to us until you've checked that it's really a problem with the main site. If it's only a problem with the mirror, you should @@ -399,7 +399,7 @@ setting up a mirror. You already have permission. If the mirror is in a country where we don't already have plenty of mirrors, we may be willing to add it to the list on our -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html}{mirrors +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html}{mirrors page}. Read the guidelines on that page, make sure your mirror works, and email us the information listed at the bottom of the page. @@ -414,7 +414,7 @@ to be a cheap way to gain search rankings. If you have technical questions about the process of mirroring, then you might want to mail us before setting up the mirror (see also the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html#guidelines}{guidelines on the Mirrors page}); +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html#guidelines}{guidelines on the Mirrors page}); but if you just want to ask for permission, you don't need to. You already have permission. diff --git a/doc/index.but b/doc/index.but index 1e71234f7..0877ee90d 100644 --- a/doc/index.but +++ b/doc/index.but @@ -340,6 +340,7 @@ saved sessions from \IM{remote-controlled printing} ANSI printing \IM{remote-controlled printing} remote-controlled printing \IM{remote-controlled printing} printing, remote-controlled +\IM{remote-controlled printing} passthrough printing \IM{Home and End keys} Home key \IM{Home and End keys} End key @@ -829,9 +830,6 @@ saved sessions from \IM{login scripts}{startup scripts} login scripts \IM{login scripts}{startup scripts} startup scripts -\IM{WS2_32.DLL} \cw{WS2_32.DLL} -\IM{WS2_32.DLL} WinSock version 2 - \IM{Red Hat Linux} Red Hat Linux \IM{Red Hat Linux} Linux, Red Hat diff --git a/doc/man-pl.but b/doc/man-pl.but index a46e6a196..58ca7a289 100644 --- a/doc/man-pl.but +++ b/doc/man-pl.but @@ -182,6 +182,15 @@ which of the agent's keys to use. } \dd Allow use of an authentication agent. (This option is only necessary to override a setting in a saved session.) +\dt \cw{\-noshare} + +\dd Don't test and try to share an existing connection, always make +a new connection. + +\dt \cw{\-share} + +\dd Test and try to share an existing connection. + \dt \cw{\-hostkey} \e{key} \dd Specify an acceptable host public key. This option may be specified @@ -260,7 +269,7 @@ exists, nonzero otherwise. For more information on plink, it's probably best to go and look at the manual on the PuTTY web page: -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} \S{plink-manpage-bugs} BUGS diff --git a/doc/man-pscp.but b/doc/man-pscp.but index 05e5a23cb..6c703e138 100644 --- a/doc/man-pscp.but +++ b/doc/man-pscp.but @@ -174,7 +174,7 @@ encrypted packet data. For more information on \cw{pscp} it's probably best to go and look at the manual on the PuTTY web page: -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} \S{pscp-manpage-bugs} BUGS diff --git a/doc/man-psft.but b/doc/man-psft.but index 80d86ecf6..51f30d3a0 100644 --- a/doc/man-psft.but +++ b/doc/man-psft.but @@ -159,7 +159,7 @@ at the \cw{psftp>} prompt. For more information on \cw{psftp} it's probably best to go and look at the manual on the PuTTY web page: -\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} +\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} \S{psftp-manpage-bugs} BUGS diff --git a/doc/man-ptel.but b/doc/man-ptel.but index a3b794059..73b85ecce 100644 --- a/doc/man-ptel.but +++ b/doc/man-ptel.but @@ -208,7 +208,7 @@ your home directory. For more information on PuTTY and PuTTYtel, it's probably best to go and look at the manual on the web page: -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} \S{puttytel-manpage-bugs} BUGS diff --git a/doc/man-putt.but b/doc/man-putt.but index df7b9e1fe..cb7cca470 100644 --- a/doc/man-putt.but +++ b/doc/man-putt.but @@ -321,7 +321,7 @@ your home directory. For more information on PuTTY, it's probably best to go and look at the manual on the web page: -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} \S{putty-manpage-bugs} BUGS diff --git a/doc/pgpkeys.but b/doc/pgpkeys.but index 9ec900665..71143af22 100644 --- a/doc/pgpkeys.but +++ b/doc/pgpkeys.but @@ -53,19 +53,19 @@ The current issue of those keys are available for download from the PuTTY website, and are also available on PGP keyservers using the key IDs listed below. -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2015.asc}{\s{Master Key}} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2015.asc}{\s{Master Key}} \dd RSA, 4096-bit. Key ID: \cw{4096R/04676F7C} (long version: \cw{4096R/AB585DC604676F7C}). Fingerprint: \cw{440D\_E3B5\_B7A1\_CA85\_B3CC\_\_1718\_AB58\_5DC6\_0467\_6F7C} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2015.asc}{\s{Release Key}} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2015.asc}{\s{Release Key}} \dd RSA, 2048-bit. Key ID: \cw{2048R/B43434E4} (long version: \cw{2048R/9DFE2648B43434E4}). Fingerprint: \cw{0054\_DDAA\_8ADA\_15D2\_768A\_\_6DE7\_9DFE\_2648\_B434\_34E4} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2016.asc}{\s{Secure Contact Key}} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2016.asc}{\s{Secure Contact Key}} \dd RSA, 2048-bit. Main key ID: \cw{2048R/8A0AF00B} (long version: \cw{2048R/C4FCAAD08A0AF00B}). Encryption subkey ID: @@ -73,7 +73,7 @@ IDs listed below. Fingerprint: \cw{8A26\_250E\_763F\_E359\_75F3\_\_118F\_C4FC\_AAD0\_8A0A\_F00B} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2015.asc}{\s{Snapshot Key}} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2015.asc}{\s{Snapshot Key}} \dd RSA, 2048-bit. Key ID: \cw{2048R/D15F7E8A} (long version: \cw{2048R/EEF20295D15F7E8A}). Fingerprint: @@ -179,37 +179,37 @@ Releases prior to the rollover are signed with the old Release Keys. For completeness, those old keys are given here: -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-rsa.asc}{\s{Master Key} (original RSA)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-rsa.asc}{\s{Master Key} (original RSA)} \dd RSA, 1024-bit. Key ID: \cw{1024R/1E34AC41} (long version: \cw{1024R/9D5877BF1E34AC41}). Fingerprint: \cw{8F\_15\_97\_DA\_25\_30\_AB\_0D\_\_88\_D1\_92\_54\_11\_CF\_0C\_4C} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-dsa.asc}{\s{Master Key} (original DSA)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-dsa.asc}{\s{Master Key} (original DSA)} \dd DSA, 1024-bit. Key ID: \cw{1024D/6A93B34E} (long version: \cw{1024D/4F5E6DF56A93B34E}). Fingerprint: \cw{313C\_3E76\_4B74\_C2C5\_F2AE\_\_83A8\_4F5E\_6DF5\_6A93\_B34E} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-rsa.asc}{\s{Release Key} (original RSA)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-rsa.asc}{\s{Release Key} (original RSA)} \dd RSA, 1024-bit. Key ID: \cw{1024R/B41CAE29} (long version: \cw{1024R/EF39CCC0B41CAE29}). Fingerprint: \cw{AE\_65\_D3\_F7\_85\_D3\_18\_E0\_\_3B\_0C\_9B\_02\_FF\_3A\_81\_FE} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-dsa.asc}{\s{Release Key} (original DSA)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-dsa.asc}{\s{Release Key} (original DSA)} \dd DSA, 1024-bit. Key ID: \cw{1024D/08B0A90B} (long version: \cw{1024D/FECD6F3F08B0A90B}). Fingerprint: \cw{00B1\_1009\_38E6\_9800\_6518\_\_F0AB\_FECD\_6F3F\_08B0\_A90B} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-rsa.asc}{\s{Snapshot Key} (original RSA)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-rsa.asc}{\s{Snapshot Key} (original RSA)} \dd RSA, 1024-bit. Key ID: \cw{1024R/32B903A9} (long version: \cw{1024R/FAAED21532B903A9}). Fingerprint: \cw{86\_8B\_1F\_79\_9C\_F4\_7F\_BD\_\_8B\_1B\_D7\_8E\_C6\_4E\_4C\_03} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-dsa.asc}{\s{Snapshot Key} (original DSA)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-dsa.asc}{\s{Snapshot Key} (original DSA)} \dd DSA, 1024-bit. Key ID: \cw{1024D/7D3E4A00} (long version: \cw{1024D/165E56F77D3E4A00}). Fingerprint: diff --git a/doc/plink.but b/doc/plink.but index 351e13ea7..74da18a1a 100644 --- a/doc/plink.but +++ b/doc/plink.but @@ -41,7 +41,7 @@ use Plink: \c Z:\sysosd>plink \c Plink: command-line connection utility -\c Release 0.68 +\c Release 0.70 \c Usage: plink [options] [user@]host [command] \c ("host" can also be a PuTTY saved session name) \c Options: @@ -75,6 +75,8 @@ use Plink: \c -i key private key file for user authentication \c -noagent disable use of Pageant \c -agent enable use of Pageant +\c -noshare disable use of connection sharing +\c -share enable use of connection sharing \c -hostkey aa:bb:cc:... \c manually specify a host key (may be repeated) \c -m file read remote command(s) from file @@ -237,6 +239,28 @@ line. (This option is only meaningful with the SSH-2 protocol.) +\S2{plink-option-share} \I{-share-plink}\c{-share}: +Test and try to share an existing connection. + +This option tris to detect if an existing connection can be shared +(See \k{config-ssh-sharing} for more information about SSH connection +sharing.) and reuses that connection. + +A Plink invocation of the form: + +\c plink -share +\e iiiiiiiii + +will test whether there is currently a viable \q{upstream} for the +session in question, which can be specified using any syntax you'd +normally use with Plink to make an actual connection (a host/port +number, a bare saved session name, \c{-load}, etc). If no \q{upstream} +viable session is found and \c{-share} is specified, this connection +will be become the \q{upstream} connection for subsequent connection +sharing tries. + +(This option is only meaningful with the SSH-2 protocol.) + \S2{plink-option-shareexists} \I{-shareexists-plink}\c{-shareexists}: test for connection-sharing upstream diff --git a/doc/pscp.but b/doc/pscp.but index 27643a468..7b90810b5 100644 --- a/doc/pscp.but +++ b/doc/pscp.but @@ -39,7 +39,7 @@ use PSCP: \c Z:\owendadmin>pscp \c PuTTY Secure Copy client -\c Release 0.68 +\c Release 0.70 \c Usage: pscp [options] [user@]host:source target \c pscp [options] source [source...] [user@]host:target \c pscp [options] -ls [user@]host:filespec diff --git a/doc/udp.but b/doc/udp.but index c50464ee1..b71688b7e 100644 --- a/doc/udp.but +++ b/doc/udp.but @@ -138,9 +138,9 @@ construct. Use these wherever possible. \H{udp-multi-compiler} Independence of specific compiler -Windows PuTTY can currently be compiled with any of four Windows -compilers: MS Visual C, Borland's freely downloadable C compiler, -the Cygwin / \cw{mingw32} GNU tools, and \cw{lcc-win32}. +Windows PuTTY can currently be compiled with any of three Windows +compilers: MS Visual C, the Cygwin / \cw{mingw32} GNU tools, and +\cw{clang} (in MS compatibility mode). This is a really useful property of PuTTY, because it means people who want to contribute to the coding don't depend on having a @@ -331,7 +331,7 @@ local state structures \c{s} or \c{st} in each function, or the backend-wide structure \c{ssh}. See -\W{http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html}\c{http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html} +\W{https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html}\c{https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html} for a more in-depth discussion of what these macros are for and how they work. diff --git a/doc/using.but b/doc/using.but index 7d184b7c2..f5e3b57bd 100644 --- a/doc/using.but +++ b/doc/using.but @@ -1042,3 +1042,15 @@ any processes started with Duplicate Session, New Session etc. (However, if you're invoking PuTTY tools explicitly, for instance as a proxy command, you'll need to arrange to pass them the \c{-restrict-acl} option yourself, if that's what you want.) + +If Pageant is started with the \c{-restrict-acl} option, and you use +it to launch a PuTTY session from its System Tray submenu, then +Pageant will \e{not} default to starting the PuTTY subprocess with a +restricted ACL. This is because PuTTY is more likely to suffer reduced +functionality as a result of restricted ACLs (e.g. screen reader +software will have a greater need to interact with it), whereas +Pageant stores the more critical information (hence benefits more from +the extra protection), so it's reasonable to want to run Pageant but +not PuTTY with the ACL restrictions. You can force Pageant to start +subsidiary PuTTY processes with a restricted ACL if you also pass the +\c{-restrict-putty-acl} option. diff --git a/fuzzterm.c b/fuzzterm.c index 15b5d6354..667d2eea5 100644 --- a/fuzzterm.c +++ b/fuzzterm.c @@ -45,7 +45,7 @@ int from_backend(void *frontend, int is_stderr, const char *data, int len) void request_resize(void *frontend, int x, int y) { } void do_text(Context ctx, int x, int y, wchar_t * text, int len, - unsigned long attr, int lattr) + unsigned long attr, int lattr, truecolour tc) { int i; @@ -56,7 +56,7 @@ void do_text(Context ctx, int x, int y, wchar_t * text, int len, printf("\n"); } void do_cursor(Context ctx, int x, int y, wchar_t * text, int len, - unsigned long attr, int lattr) + unsigned long attr, int lattr, truecolour tc) { int i; @@ -81,13 +81,13 @@ Context get_ctx(void *frontend) { void free_ctx(Context ctx) { } void palette_set(void *frontend, int a, int b, int c, int d) { } void palette_reset(void *frontend) { } -void write_clip(void *frontend, wchar_t *a, int *b, int c, int d) { } -void get_clip(void *frontend, wchar_t **w, int *i) { } +int palette_get(void *frontend, int n, int *r, int *g, int *b) {return FALSE;} +void write_clip(void *frontend, int clipboard, + wchar_t *a, int *b, truecolour *c, int d, int e) { } void set_raw_mouse_mode(void *frontend, int m) { } -void request_paste(void *frontend) { } +void frontend_request_paste(void *frontend, int clipboard) { } void do_beep(void *frontend, int a) { } void sys_cursor(void *frontend, int x, int y) { } -void fatalbox(const char *fmt, ...) { exit(0); } void modalfatalbox(const char *fmt, ...) { exit(0); } void nonfatal(const char *fmt, ...) { } diff --git a/import.c b/import.c index adf68777d..88589a71d 100644 --- a/import.c +++ b/import.c @@ -445,7 +445,7 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, if (!strcmp(p, "ENCRYPTED")) ret->encrypted = TRUE; } else if (!strcmp(line, "DEK-Info")) { - int i, j, ivlen; + int i, ivlen; if (!strncmp(p, "DES-EDE3-CBC,", 13)) { ret->encryption = OP_E_3DES; @@ -459,6 +459,7 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, } p = strchr(p, ',') + 1;/* always non-NULL, by above checks */ for (i = 0; i < ivlen; i++) { + unsigned j; if (1 != sscanf(p, "%2x", &j)) { errmsg = "expected more iv data in DEK-Info"; goto error; diff --git a/logging.c b/logging.c index 865fe9b82..c36342746 100644 --- a/logging.c +++ b/logging.c @@ -351,7 +351,7 @@ void log_packet(void *handle, int direction, int type, } dumpdata[10+2+3*(p%16)] = smalldata[0]; dumpdata[10+2+3*(p%16)+1] = smalldata[1]; - dumpdata[10+1+3*16+2+(p%16)] = (isprint(c) ? c : '.'); + dumpdata[10+1+3*16+2+(p%16)] = (c >= 0x20 && c < 0x7F ? c : '.'); output_pos = (p%16) + 1; } diff --git a/minibidi.c b/minibidi.c index 6c0621162..2bdf4deb3 100644 --- a/minibidi.c +++ b/minibidi.c @@ -3,7 +3,7 @@ * ------------ * Description: * ------------ - * This is an implemention of Unicode's Bidirectional Algorithm + * This is an implementation of Unicode's Bidirectional Algorithm * (known as UAX #9). * * http://www.unicode.org/reports/tr9/ @@ -89,7 +89,7 @@ enum { /* Shaping Types */ enum { - SL, /* Left-Joining, doesnt exist in U+0600 - U+06FF */ + SL, /* Left-Joining, doesn't exist in U+0600 - U+06FF */ SR, /* Right-Joining, ie has Isolated, Final */ SD, /* Dual-Joining, ie has Isolated, Final, Initial, Medial */ SU, /* Non-Joining */ @@ -2014,7 +2014,7 @@ int main(int argc, char **argv) unsigned long chr = strtoul(argv[i], NULL, 0); int type = getType(chr); assert(typetoname[type].type == type); - printf("U+%04x: %s\n", chr, typetoname[type].name); + printf("U+%04x: %s\n", (unsigned)chr, typetoname[type].name); } return 0; diff --git a/misc.c b/misc.c index 9aff234b9..fc5b149dd 100644 --- a/misc.c +++ b/misc.c @@ -157,7 +157,8 @@ int main(void) passes++; \ } else { \ printf("fail: %s(%s,%s)%s = %u, expected %u\n", \ - #func, #string, #arg2, #suffix, ret, result); \ + #func, #string, #arg2, #suffix, ret, \ + (unsigned)result); \ fails++; \ } \ } while (0) @@ -1165,11 +1166,21 @@ char *buildinfo(const char *newline) BUILDINFO_PLATFORM); #ifdef __clang_version__ +#define FOUND_COMPILER strbuf_catf(buf, "%sCompiler: clang %s", newline, __clang_version__); #elif defined __GNUC__ && defined __VERSION__ +#define FOUND_COMPILER strbuf_catf(buf, "%sCompiler: gcc %s", newline, __VERSION__); -#elif defined _MSC_VER - strbuf_catf(buf, "%sCompiler: Visual Studio", newline); +#endif + +#if defined _MSC_VER +#ifndef FOUND_COMPILER +#define FOUND_COMPILER + strbuf_catf(buf, "%sCompiler: ", newline); +#else + strbuf_catf(buf, ", emulating "); +#endif + strbuf_catf(buf, "Visual Studio", newline); #if _MSC_VER == 1900 strbuf_catf(buf, " 2015 / MSVC++ 14.0"); #elif _MSC_VER == 1800 @@ -1178,12 +1189,14 @@ char *buildinfo(const char *newline) strbuf_catf(buf, " 2012 / MSVC++ 11.0"); #elif _MSC_VER == 1600 strbuf_catf(buf, " 2010 / MSVC++ 10.0"); -#elif _MSC_VER == 1500 +#elif _MSC_VER == 1500 strbuf_catf(buf, " 2008 / MSVC++ 9.0"); -#elif _MSC_VER == 1400 +#elif _MSC_VER == 1400 strbuf_catf(buf, " 2005 / MSVC++ 8.0"); -#elif _MSC_VER == 1310 +#elif _MSC_VER == 1310 strbuf_catf(buf, " 2003 / MSVC++ 7.1"); +#elif _MSC_VER == 1300 + strbuf_catf(buf, " 2003 / MSVC++ 7.0"); #else strbuf_catf(buf, ", unrecognised version"); #endif @@ -1201,6 +1214,9 @@ char *buildinfo(const char *newline) } #endif +#if defined _WINDOWS && defined MINEFIELD + strbuf_catf(buf, "%sBuild option: MINEFIELD", newline); +#endif #ifdef NO_SECURITY strbuf_catf(buf, "%sBuild option: NO_SECURITY", newline); #endif diff --git a/mkfiles.pl b/mkfiles.pl index ae15ac488..a0a78fee1 100755 --- a/mkfiles.pl +++ b/mkfiles.pl @@ -268,7 +268,7 @@ ($) # Returns true if the argument is a known makefile type. Otherwise, # prints a warning and returns false; if (grep { $type eq $_ } - ("vc","vcproj","cygwin","borland","lcc","devcppproj","gtk","unix", + ("vc","vcproj","cygwin","lcc","devcppproj","gtk","unix", "am","osx","vstudio10","vstudio12","clangcl")) { return 1; } @@ -364,7 +364,7 @@ sub splitline { $len = (defined $width ? $width : 76); $splitchar = (defined $splitchar ? $splitchar : '\\'); while (length $line > $len) { - $line =~ /^(.{0,$len})\s(.*)$/ or $line =~ /^(.{$len,}?\s(.*)$/; + $line =~ /^(.{0,$len})\s(.*)$/ or $line =~ /^(.{$len,})?\s(.*)$/; $result .= $1; $result .= " ${splitchar}\n\t\t" if $2 ne ''; $line = $2; @@ -492,6 +492,31 @@ sub manpages { # paths in $LIB) it's reasonable to have the choice of # compilation target driven by another environment variable # set in parallel with that one. + # - for older versions of the VS libraries you may also have to + # set EXTRA_console and/or EXTRA_windows to the name of an + # object file manually extracted from one of those libraries. + # * This is because old VS seems to manage its startup code by + # having libcmt.lib contain lots of *crt0.obj objects, one + # for each possible user entry point (main, WinMain and the + # wide-char versions of both), of which the linker arranges + # to include the right one by special-case code. But lld + # only seems to mimic half of that code - it does include + # the right crt0 object, but it doesn't also deliberately + # _avoid_ including the _wrong_ ones, and since all those + # objects define a common set of global symbols for other + # parts of the library to use, lld may well select an + # arbitrary one of them the first time it sees a reference + # to one of those global symbols, and then later also select + # the _right_ one for the application's entry point, causing + # a multiple-definitions crash. + # * So the workaround is to explicitly include the right + # *crt0.obj file on the linker command line before lld even + # begins searching libraries. Hence, for a console + # application, you might extract crt0.obj from the library + # in question and set EXTRA_console=crt0.obj, and for a GUI + # application, do the same with wincrt0.obj. Then this + # makefile will include the right one of those objects + # alongside the matching /subsystem linker option. open OUT, ">$makefiles{'clangcl'}"; select OUT; print @@ -519,7 +544,8 @@ sub manpages { &splitline("CFLAGS = /nologo /W3 /O1 " . (join " ", map {"-I$dirpfx$_"} @srcdirs) . " /D_WINDOWS /D_WIN32_WINDOWS=0x500 /DWINVER=0x500 ". - "/D_CRT_SECURE_NO_WARNINGS")."\n". + "/D_CRT_SECURE_NO_WARNINGS /D_WINSOCK_DEPRECATED_NO_WARNINGS"). + "\n". "LFLAGS = /incremental:no /dynamicbase /nxcompat\n". &splitline("RCFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs). " -DWIN32 -D_WIN32 -DWINVER=0x0400")."\n". @@ -531,21 +557,22 @@ sub manpages { print "\n\n"; foreach $p (&prognames("G:C")) { ($prog, $type) = split ",", $p; - $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res.o", undef); + $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", undef); print &splitline("\$(BUILDDIR)$prog.exe: " . $objstr), "\n"; - $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res.o", "X.lib"); + $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", "X.lib"); $subsys = ($type eq "G") ? "windows" : "console"; print &splitline("\t\$(LD) \$(LFLAGS) \$(XLFLAGS) ". "/out:\$(BUILDDIR)$prog.exe ". "/lldmap:\$(BUILDDIR)$prog.map ". - "/subsystem:$subsys\$(SUBSYSVER) $objstr")."\n\n"; + "/subsystem:$subsys\$(SUBSYSVER) ". + "\$(EXTRA_$subsys) $objstr")."\n\n"; } - foreach $d (&deps("\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res.o", $dirpfx, "/", "vc")) { + foreach $d (&deps("\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", $dirpfx, "/", "vc")) { $extradeps = $forceobj{$d->{obj_orig}} ? ["*.c","*.h","*.rc"] : []; print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @$extradeps, @{$d->{deps}})), "\n"; - if ($d->{obj} =~ /\.res\.o$/) { + if ($d->{obj} =~ /\.res$/) { print "\t\$(RC) \$(RCFLAGS) ".$d->{deps}->[0]." -o ".$d->{obj}."\n\n"; } else { print "\t\$(CC) /Fo\$(BUILDDIR) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) /c \$<\n\n"; @@ -555,7 +582,7 @@ sub manpages { print &def($makefile_extra{'clangcl'}->{'end'}); print "\nclean:\n". &splitline("\trm -f \$(BUILDDIR)*.obj \$(BUILDDIR)*.exe ". - "\$(BUILDDIR)*.res.o \$(BUILDDIR)*.map ". + "\$(BUILDDIR)*.res \$(BUILDDIR)*.map ". "\$(BUILDDIR)*.exe.manifest")."\n"; select STDOUT; close OUT; } @@ -633,121 +660,6 @@ sub manpages { } -##-- Borland makefile -if (defined $makefiles{'borland'}) { - $dirpfx = &dirpfx($makefiles{'borland'}, "\\"); - - %stdlibs = ( # Borland provides many Win32 API libraries intrinsically - "advapi32" => 1, - "comctl32" => 1, - "comdlg32" => 1, - "gdi32" => 1, - "imm32" => 1, - "shell32" => 1, - "user32" => 1, - "winmm" => 1, - "winspool" => 1, - "wsock32" => 1, - ); - open OUT, ">$makefiles{'borland'}"; select OUT; - print - "# Makefile for $project_name under Borland C.\n". - "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". - "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; - # bcc32 command line option is -D not /D - ($_ = $help) =~ s/([=" ])\/D/$1-D/gs; - print $_; - print - "\n". - "# If you rename this file to `Makefile', you should change this line,\n". - "# so that the .rsp files still depend on the correct makefile.\n". - "MAKEFILE = Makefile.bor\n". - "\n". - "# C compilation flags\n". - "CFLAGS = -D_WINDOWS -DWINVER=0x0500\n". - "# Resource compilation flags\n". - "RCFLAGS = -DNO_WINRESRC_H -DWIN32 -D_WIN32 -DWINVER=0x0401\n". - "\n". - "# Get include directory for resource compiler\n". - "!if !\$d(BCB)\n". - "BCB = \$(MAKEDIR)\\..\n". - "!endif\n". - "\n". - &def($makefile_extra{'borland'}->{'vars'}) . - "\n". - ".c.obj:\n". - &splitline("\tbcc32 -w-aus -w-ccc -w-par -w-pia \$(COMPAT)". - " \$(CFLAGS) \$(XFLAGS) ". - (join " ", map {"-I$dirpfx$_"} @srcdirs) . - " /c \$*.c",69)."\n". - ".rc.res:\n". - &splitline("\tbrcc32 \$(RCFL) -i \$(BCB)\\include -r". - " \$(RCFLAGS) \$*.rc",69)."\n". - "\n"; - print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C")); - print "\n\n"; - foreach $p (&prognames("G:C")) { - ($prog, $type) = split ",", $p; - $objstr = &objects($p, "X.obj", "X.res", undef); - print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n"; - my $ap = ($type eq "G") ? "-aa" : "-ap"; - print "\tilink32 $ap -Gn -L\$(BCB)\\lib \@$prog.rsp\n\n"; - } - foreach $p (&prognames("G:C")) { - ($prog, $type) = split ",", $p; - print $prog, ".rsp: \$(MAKEFILE)\n"; - $objstr = &objects($p, "X.obj", undef, undef); - @objlist = split " ", $objstr; - @objlines = (""); - foreach $i (@objlist) { - if (length($objlines[$#objlines] . " $i") > 50) { - push @objlines, ""; - } - $objlines[$#objlines] .= " $i"; - } - $c0w = ($type eq "G") ? "c0w32" : "c0x32"; - print "\techo $c0w + > $prog.rsp\n"; - for ($i=0; $i<=$#objlines; $i++) { - $plus = ($i < $#objlines ? " +" : ""); - print "\techo$objlines[$i]$plus >> $prog.rsp\n"; - } - print "\techo $prog.exe >> $prog.rsp\n"; - $objstr = &objects($p, "X.obj", "X.res", undef); - @libs = split " ", &objects($p, undef, undef, "X"); - @libs = grep { !$stdlibs{$_} } @libs; - unshift @libs, "cw32", "import32"; - $libstr = join ' ', @libs; - print "\techo nul,$libstr, >> $prog.rsp\n"; - print "\techo " . &objects($p, undef, "X.res", undef) . " >> $prog.rsp\n"; - print "\n"; - } - foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\", "borland")) { - if ($forceobj{$d->{obj_orig}}) { - printf("%s: FORCE\n", $d->{obj}); - } else { - print &splitline(sprintf("%s: %s", $d->{obj}, - join " ", @{$d->{deps}})), "\n"; - } - } - print "\n"; - print &def($makefile_extra{'borland'}->{'end'}); - print "\nclean:\n". - "\t-del *.obj\n". - "\t-del *.exe\n". - "\t-del *.res\n". - "\t-del *.pch\n". - "\t-del *.aps\n". - "\t-del *.il*\n". - "\t-del *.pdb\n". - "\t-del *.rsp\n". - "\t-del *.tds\n". - "\t-del *.\$\$\$\$\$\$\n". - "\n". - "FORCE:\n". - "\t-rem dummy command\n"; - select STDOUT; close OUT; -} - if (defined $makefiles{'vc'}) { $dirpfx = &dirpfx($makefiles{'vc'}, "\\"); @@ -1966,7 +1878,7 @@ sub manpages { "# ** DO NOT EDIT **\r\n". "\r\n". # No difference between DEBUG and RELEASE here as in 'vcproj', because - # Dev-C++ does not support mutiple compilation profiles in one single project. + # Dev-C++ does not support multiple compilation profiles in one single project. # (At least I can say this for Dev-C++ 5 Beta) "[Project]\r\n". "FileName=$windows_project.dev\r\n". diff --git a/network.h b/network.h index d58635b62..0941f721f 100644 --- a/network.h +++ b/network.h @@ -64,12 +64,12 @@ struct plug_function_table { * proxy command, so the receiver should probably prefix it to * indicate this. */ - int (*closing) + void (*closing) (Plug p, const char *error_msg, int error_code, int calling_back); /* error_msg is NULL iff it is not an error (ie it closed normally) */ /* calling_back != 0 iff there is a Plug function */ /* currently running (would cure the fixme in try_send()) */ - int (*receive) (Plug p, int urgent, char *data, int len); + void (*receive) (Plug p, int urgent, char *data, int len); /* * - urgent==0. `data' points to `len' bytes of perfectly * ordinary data. diff --git a/nocmdline.c b/nocmdline.c new file mode 100644 index 000000000..a76433857 --- /dev/null +++ b/nocmdline.c @@ -0,0 +1,42 @@ +/* + * nocmdline.c - stubs in applications which don't do the + * standard(ish) PuTTY tools' command-line parsing + */ + +#include +#include +#include +#include "putty.h" + +/* + * Stub version of the function in cmdline.c which provides the + * password to SSH authentication by remembering it having been passed + * as a command-line option. If we're not doing normal command-line + * handling, then there is no such option, so that function always + * returns failure. + */ +int cmdline_get_passwd_input(prompts_t *p, const unsigned char *in, int inlen) +{ + return -1; +} + +/* + * The main cmdline_process_param function is normally called from + * applications' main(). An application linking against this stub + * module shouldn't have a main() that calls it in the first place :-) + * but it is just occasionally called by other supporting functions, + * such as one in uxputty.c which sometimes handles a non-option + * argument by making up equivalent options and passing them back to + * this function. So we have to provide a link-time stub of this + * function, but it had better not end up being called at run time. + */ +int cmdline_process_param(const char *p, char *value, + int need_save, Conf *conf) +{ + assert(FALSE && "cmdline_process_param should never be called"); +} + +/* + * This variable will be referred to, so it has to exist. It's ignored. + */ +int cmdline_tooltype = 0; diff --git a/pageant.c b/pageant.c index 2d9a74023..a168e522e 100644 --- a/pageant.c +++ b/pageant.c @@ -964,11 +964,11 @@ int pageant_delete_ssh2_key(struct ssh2_userkey *skey) * Coroutine macros similar to, but simplified from, those in ssh.c. */ #define crBegin(v) { int *crLine = &v; switch(v) { case 0:; -#define crFinish(z) } *crLine = 0; return (z); } +#define crFinishV } *crLine = 0; return; } #define crGetChar(c) do \ { \ while (len == 0) { \ - *crLine =__LINE__; return 1; case __LINE__:; \ + *crLine =__LINE__; return; case __LINE__:; \ } \ len--; \ (c) = (unsigned char)*data++; \ @@ -987,8 +987,8 @@ struct pageant_conn_state { int crLine; /* for coroutine in pageant_conn_receive */ }; -static int pageant_conn_closing(Plug plug, const char *error_msg, - int error_code, int calling_back) +static void pageant_conn_closing(Plug plug, const char *error_msg, + int error_code, int calling_back) { struct pageant_conn_state *pc = (struct pageant_conn_state *)plug; if (error_msg) @@ -997,7 +997,6 @@ static int pageant_conn_closing(Plug plug, const char *error_msg, plog(pc->logctx, pc->logfn, "%p: connection closed", pc); sk_close(pc->connsock); sfree(pc); - return 1; } static void pageant_conn_sent(Plug plug, int bufsize) @@ -1021,7 +1020,7 @@ static void pageant_conn_log(void *logctx, const char *fmt, va_list ap) sfree(formatted); } -static int pageant_conn_receive(Plug plug, int urgent, char *data, int len) +static void pageant_conn_receive(Plug plug, int urgent, char *data, int len) { struct pageant_conn_state *pc = (struct pageant_conn_state *)plug; char c; @@ -1065,7 +1064,7 @@ static int pageant_conn_receive(Plug plug, int urgent, char *data, int len) } } - crFinish(1); + crFinishV; } struct pageant_listen_state { @@ -1077,15 +1076,14 @@ struct pageant_listen_state { pageant_logfn_t logfn; }; -static int pageant_listen_closing(Plug plug, const char *error_msg, - int error_code, int calling_back) +static void pageant_listen_closing(Plug plug, const char *error_msg, + int error_code, int calling_back) { struct pageant_listen_state *pl = (struct pageant_listen_state *)plug; if (error_msg) plog(pl->logctx, pl->logfn, "listening socket: error: %s", error_msg); sk_close(pl->listensock); pl->listensock = NULL; - return 1; } static int pageant_listen_accepting(Plug plug, @@ -1469,7 +1467,7 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, } /* - * If we get here, we've succesfully loaded the key into + * If we get here, we've successfully loaded the key into * rkey/skey, but not yet added it to the agent. */ diff --git a/portfwd.c b/portfwd.c index 8a73a182d..b68e4bb8f 100644 --- a/portfwd.c +++ b/portfwd.c @@ -117,8 +117,8 @@ static void pfl_log(Plug plug, int type, SockAddr addr, int port, /* we have to dump these since we have no interface to logging.c */ } -static int pfd_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +static void pfd_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) { struct PortForwarding *pf = (struct PortForwarding *) plug; @@ -145,16 +145,13 @@ static int pfd_closing(Plug plug, const char *error_msg, int error_code, if (pf->c) sshfwd_write_eof(pf->c); } - - return 1; } -static int pfl_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +static void pfl_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) { struct PortListener *pl = (struct PortListener *) plug; pfl_terminate(pl); - return 1; } static void wrap_send_port_open(void *channel, const char *hostname, int port, @@ -172,7 +169,7 @@ static void wrap_send_port_open(void *channel, const char *hostname, int port, sfree(description); } -static int pfd_receive(Plug plug, int urgent, char *data, int len) +static void pfd_receive(Plug plug, int urgent, char *data, int len) { struct PortForwarding *pf = (struct PortForwarding *) plug; if (pf->dynamic) { @@ -204,7 +201,7 @@ static int pfd_receive(Plug plug, int urgent, char *data, int len) data[1] = 91; /* generic `request rejected' */ sk_write(pf->s, data, 8); pfd_close(pf); - return 1; + return; } if (pf->sockslen <= 8) continue; /* haven't started user/hostname */ @@ -320,7 +317,7 @@ static int pfd_receive(Plug plug, int urgent, char *data, int len) reply[1] = 1; /* generic failure */ sk_write(pf->s, (char *) reply, lenof(reply)); pfd_close(pf); - return 1; + return; } /* * Now we have a viable connect request. Switch @@ -350,7 +347,7 @@ static int pfd_receive(Plug plug, int urgent, char *data, int len) reply[1] = 8; /* atype not supported */ sk_write(pf->s, (char *) reply, lenof(reply)); pfd_close(pf); - return 1; + return; } } } @@ -362,9 +359,9 @@ static int pfd_receive(Plug plug, int urgent, char *data, int len) * close the connection rudely. */ pfd_close(pf); - return 1; + break; } - return 1; + return; /* * We come here when we're ready to make an actual @@ -383,7 +380,7 @@ static int pfd_receive(Plug plug, int urgent, char *data, int len) pf->c = new_sock_channel(pf->backhandle, pf); if (pf->c == NULL) { pfd_close(pf); - return 1; + return; } else { /* asks to forward to the specified host/port for this */ wrap_send_port_open(pf->c, pf->hostname, pf->port, pf->s); @@ -406,7 +403,6 @@ static int pfd_receive(Plug plug, int urgent, char *data, int len) sk_set_frozen(pf->s, 1); } } - return 1; } static void pfd_sent(Plug plug, int bufsize) diff --git a/proxy.c b/proxy.c index 52006794c..68b7e9a06 100644 --- a/proxy.c +++ b/proxy.c @@ -201,8 +201,8 @@ static void plug_proxy_log(Plug plug, int type, SockAddr addr, int port, plug_log(ps->plug, type, addr, port, error_msg, error_code); } -static int plug_proxy_closing (Plug p, const char *error_msg, - int error_code, int calling_back) +static void plug_proxy_closing (Plug p, const char *error_msg, + int error_code, int calling_back) { Proxy_Plug pp = (Proxy_Plug) p; Proxy_Socket ps = pp->proxy_socket; @@ -211,13 +211,13 @@ static int plug_proxy_closing (Plug p, const char *error_msg, ps->closing_error_msg = error_msg; ps->closing_error_code = error_code; ps->closing_calling_back = calling_back; - return ps->negotiate(ps, PROXY_CHANGE_CLOSING); + ps->negotiate(ps, PROXY_CHANGE_CLOSING); + } else { + plug_closing(ps->plug, error_msg, error_code, calling_back); } - return plug_closing(ps->plug, error_msg, - error_code, calling_back); } -static int plug_proxy_receive (Plug p, int urgent, char *data, int len) +static void plug_proxy_receive (Plug p, int urgent, char *data, int len) { Proxy_Plug pp = (Proxy_Plug) p; Proxy_Socket ps = pp->proxy_socket; @@ -231,9 +231,10 @@ static int plug_proxy_receive (Plug p, int urgent, char *data, int len) ps->receive_urgent = urgent; ps->receive_data = data; ps->receive_len = len; - return ps->negotiate(ps, PROXY_CHANGE_RECEIVE); + ps->negotiate(ps, PROXY_CHANGE_RECEIVE); + } else { + plug_receive(ps->plug, urgent, data, len); } - return plug_receive(ps->plug, urgent, data, len); } static void plug_proxy_sent (Plug p, int bufsize) @@ -644,9 +645,9 @@ int proxy_http_negotiate (Proxy_Socket p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - return plug_closing(p->plug, p->closing_error_msg, - p->closing_error_code, - p->closing_calling_back); + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ } if (change == PROXY_CHANGE_SENT) { @@ -847,9 +848,9 @@ int proxy_socks4_negotiate (Proxy_Socket p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - return plug_closing(p->plug, p->closing_error_msg, - p->closing_error_code, - p->closing_calling_back); + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ } if (change == PROXY_CHANGE_SENT) { @@ -987,9 +988,9 @@ int proxy_socks5_negotiate (Proxy_Socket p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - return plug_closing(p->plug, p->closing_error_msg, - p->closing_error_code, - p->closing_calling_back); + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ } if (change == PROXY_CHANGE_SENT) { @@ -1561,9 +1562,9 @@ int proxy_telnet_negotiate (Proxy_Socket p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - return plug_closing(p->plug, p->closing_error_msg, - p->closing_error_code, - p->closing_calling_back); + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ } if (change == PROXY_CHANGE_SENT) { diff --git a/pscp.c b/pscp.c index 454ec084c..87a760c2f 100644 --- a/pscp.c +++ b/pscp.c @@ -91,21 +91,6 @@ static void tell_user(FILE *stream, const char *fmt, ...) /* * Print an error message and perform a fatal exit. */ -void fatalbox(const char *fmt, ...) -{ - char *str, *str2; - va_list ap; - va_start(ap, fmt); - str = dupvprintf(fmt, ap); - str2 = dupcat("Fatal: ", str, "\n", NULL); - sfree(str); - va_end(ap); - tell_str(stderr, str2); - sfree(str2); - errs++; - - cleanup_exit(1); -} void modalfatalbox(const char *fmt, ...) { char *str, *str2; @@ -1476,7 +1461,7 @@ int scp_get_sink_action(struct scp_sink_action *act) act->action = SCP_SINK_ENDDIR; return 0; case 'T': - if (sscanf(act->buf, "%ld %*d %ld %*d", + if (sscanf(act->buf, "%lu %*d %lu %*d", &act->mtime, &act->atime) == 2) { act->settime = 1; back->send(backhandle, "", 1); diff --git a/psftp.c b/psftp.c index 5394c1fbb..643389dab 100644 --- a/psftp.c +++ b/psftp.c @@ -1050,6 +1050,8 @@ int sftp_cmd_ls(struct sftp_command *cmd) if (dirh == NULL) { printf("Unable to open %s: %s\n", dir, fxp_error()); + sfree(cdir); + sfree(unwcdir); return 0; } else { nnames = namesize = 0; @@ -1128,12 +1130,12 @@ int sftp_cmd_cd(struct sftp_command *cmd) if (cmd->nwords < 2) dir = dupstr(homedir); - else + else { dir = canonify(cmd->words[1]); - - if (!dir) { - printf("%s: canonify: %s\n", dir, fxp_error()); - return 0; + if (!dir) { + printf("%s: canonify: %s\n", cmd->words[1], fxp_error()); + return 0; + } } req = fxp_opendir_send(dir); @@ -1417,7 +1419,7 @@ int sftp_cmd_mkdir(struct sftp_command *cmd) for (i = 1; i < cmd->nwords; i++) { dir = canonify(cmd->words[i]); if (!dir) { - printf("%s: canonify: %s\n", dir, fxp_error()); + printf("%s: canonify: %s\n", cmd->words[i], fxp_error()); return 0; } @@ -2440,20 +2442,6 @@ static int verbose = 0; /* * Print an error message and perform a fatal exit. */ -void fatalbox(const char *fmt, ...) -{ - char *str, *str2; - va_list ap; - va_start(ap, fmt); - str = dupvprintf(fmt, ap); - str2 = dupcat("Fatal: ", str, "\n", NULL); - sfree(str); - va_end(ap); - fputs(str2, stderr); - sfree(str2); - - cleanup_exit(1); -} void modalfatalbox(const char *fmt, ...) { char *str, *str2; diff --git a/putty.h b/putty.h index fd2d02506..4c64cb85e 100644 --- a/putty.h +++ b/putty.h @@ -104,15 +104,16 @@ typedef struct terminal_tag Terminal; */ #define UCSWIDE 0xDFFF -#define ATTR_NARROW 0x800000U -#define ATTR_WIDE 0x400000U -#define ATTR_BOLD 0x040000U -#define ATTR_UNDER 0x080000U -#define ATTR_REVERSE 0x100000U -#define ATTR_BLINK 0x200000U -#define ATTR_FGMASK 0x0001FFU -#define ATTR_BGMASK 0x03FE00U -#define ATTR_COLOURS 0x03FFFFU +#define ATTR_NARROW 0x0800000U +#define ATTR_WIDE 0x0400000U +#define ATTR_BOLD 0x0040000U +#define ATTR_UNDER 0x0080000U +#define ATTR_REVERSE 0x0100000U +#define ATTR_BLINK 0x0200000U +#define ATTR_FGMASK 0x00001FFU +#define ATTR_BGMASK 0x003FE00U +#define ATTR_COLOURS 0x003FFFFU +#define ATTR_DIM 0x1000000U #define ATTR_FGSHIFT 0 #define ATTR_BGSHIFT 9 @@ -592,12 +593,65 @@ void prompt_ensure_result_size(prompt_t *pr, int len); /* Burn the evidence. (Assumes _all_ strings want free()ing.) */ void free_prompts(prompts_t *p); +/* + * Data type definitions for true-colour terminal display. + * 'optionalrgb' describes a single RGB colour, which overrides the + * other colour settings if 'enabled' is nonzero, and is ignored + * otherwise. 'truecolour' contains a pair of those for foreground and + * background. + */ +typedef struct optionalrgb { + unsigned char enabled; + unsigned char r, g, b; +} optionalrgb; +extern const optionalrgb optionalrgb_none; +typedef struct truecolour { + optionalrgb fg, bg; +} truecolour; +#define optionalrgb_equal(r1,r2) ( \ + (r1).enabled==(r2).enabled && \ + (r1).r==(r2).r && (r1).g==(r2).g && (r1).b==(r2).b) +#define truecolour_equal(c1,c2) ( \ + optionalrgb_equal((c1).fg, (c2).fg) && \ + optionalrgb_equal((c1).bg, (c2).bg)) + +/* + * Enumeration of clipboards. We provide some standard ones cross- + * platform, and then permit each platform to extend this enumeration + * further by defining PLATFORM_CLIPBOARDS in its own header file. + * + * CLIP_NULL is a non-clipboard, writes to which are ignored and reads + * from which return no data. + * + * CLIP_LOCAL refers to a buffer within terminal.c, which + * unconditionally saves the last data selected in the terminal. In + * configurations where a system clipboard is not written + * automatically on selection but instead by an explicit UI action, + * this is where the code responding to that action can find the data + * to write to the clipboard in question. + */ +#define CROSS_PLATFORM_CLIPBOARDS(X) \ + X(CLIP_NULL, "null clipboard") \ + X(CLIP_LOCAL, "last text selected in terminal") \ + /* end of list */ + +#define ALL_CLIPBOARDS(X) \ + CROSS_PLATFORM_CLIPBOARDS(X) \ + PLATFORM_CLIPBOARDS(X) \ + /* end of list */ + +#define CLIP_ID(id,name) id, +enum { ALL_CLIPBOARDS(CLIP_ID) N_CLIPBOARDS }; +#undef CLIP_ID + /* * Exports from the front end. */ void request_resize(void *frontend, int, int); -void do_text(Context, int, int, wchar_t *, int, unsigned long, int); -void do_cursor(Context, int, int, wchar_t *, int, unsigned long, int); +void do_text(Context, int, int, wchar_t *, int, unsigned long, int, + truecolour); +void do_cursor(Context, int, int, wchar_t *, int, unsigned long, int, + truecolour); int char_width(Context ctx, int uc); #ifdef OPTIMISE_SCROLL void do_scroll(Context, int, int, int); @@ -609,23 +663,21 @@ Context get_ctx(void *frontend); void free_ctx(Context); void palette_set(void *frontend, int, int, int, int); void palette_reset(void *frontend); -void write_aclip(void *frontend, char *, int, int); -void write_clip(void *frontend, wchar_t *, int *, int, int); -void get_clip(void *frontend, wchar_t **, int *); +int palette_get(void *frontend, int n, int *r, int *g, int *b); +void write_clip(void *frontend, int clipboard, wchar_t *, int *, + truecolour *, int, int); void optimised_move(void *frontend, int, int, int); void set_raw_mouse_mode(void *frontend, int); void connection_fatal(void *frontend, const char *, ...); void nonfatal(const char *, ...); -void fatalbox(const char *, ...); void modalfatalbox(const char *, ...); #ifdef macintosh -#pragma noreturn(fatalbox) #pragma noreturn(modalfatalbox) #endif void do_beep(void *frontend, int); void begin_session(void *frontend); void sys_cursor(void *frontend, int x, int y); -void request_paste(void *frontend); +void frontend_request_paste(void *frontend, int clipboard); void frontend_keypress(void *frontend); void frontend_echoedit_update(void *frontend, int echo, int edit); /* It's the backend's responsibility to invoke this at the start of a @@ -835,6 +887,7 @@ void cleanup_exit(int); /* Colour options */ \ X(INT, NONE, ansi_colour) \ X(INT, NONE, xterm_256_colour) \ + X(INT, NONE, true_colour) \ X(INT, NONE, system_colour) \ X(INT, NONE, try_palette) \ X(INT, NONE, bold_style) \ @@ -846,6 +899,13 @@ void cleanup_exit(int); X(INT, NONE, rtf_paste) \ X(INT, NONE, mouse_override) \ X(INT, INT, wordness) \ + X(INT, NONE, mouseautocopy) \ + X(INT, NONE, mousepaste) \ + X(INT, NONE, ctrlshiftins) \ + X(INT, NONE, ctrlshiftcv) \ + X(STR, NONE, mousepaste_custom) \ + X(STR, NONE, ctrlshiftins_custom) \ + X(STR, NONE, ctrlshiftcv_custom) \ /* translations */ \ X(INT, NONE, vtmode) \ X(STR, NONE, line_codepage) \ @@ -1028,15 +1088,17 @@ void term_mouse(Terminal *, Mouse_Button, Mouse_Button, Mouse_Action, int,int,int,int,int); void term_key(Terminal *, Key_Sym, wchar_t *, size_t, unsigned int, unsigned int); -void term_deselect(Terminal *); +void term_lost_clipboard_ownership(Terminal *, int clipboard); void term_update(Terminal *); void term_invalidate(Terminal *); void term_blink(Terminal *, int set_cursor); -void term_do_paste(Terminal *); +void term_do_paste(Terminal *, const wchar_t *, int); void term_nopaste(Terminal *); int term_ldisc(Terminal *, int option); -void term_copyall(Terminal *); +void term_copyall(Terminal *, const int *, int); void term_reconfig(Terminal *, Conf *); +void term_request_copy(Terminal *, const int *clipboards, int n_clipboards); +void term_request_paste(Terminal *, int clipboard); void term_seen_key_event(Terminal *); int term_data(Terminal *, int is_stderr, const char *data, int len); int term_data_untrusted(Terminal *, const char *data, int len); @@ -1151,6 +1213,11 @@ void pinger_free(Pinger); int conf_launchable(Conf *conf); char const *conf_dest(Conf *conf); +/* + * Exports from sessprep.c. + */ +void prepare_session(Conf *conf); + /* * Exports from sercfg.c. */ @@ -1320,8 +1387,14 @@ int cmdline_process_param(const char *, char *, int, Conf *); void cmdline_run_saved(Conf *); void cmdline_cleanup(void); int cmdline_get_passwd_input(prompts_t *p, const unsigned char *in, int inlen); +int cmdline_host_ok(Conf *); #define TOOLTYPE_FILETRANSFER 1 #define TOOLTYPE_NONNETWORK 2 +#define TOOLTYPE_HOST_ARG 4 +#define TOOLTYPE_HOST_ARG_CAN_BE_SESSION 8 +#define TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX 16 +#define TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD 32 +#define TOOLTYPE_PORT_ARG 64 extern int cmdline_tooltype; void cmdline_error(const char *, ...); @@ -1367,6 +1440,16 @@ enum { }; extern const char *const x11_authnames[]; /* declared in x11fwd.c */ +/* + * An enum for the copy-paste UI action configuration. + */ +enum { + CLIPUI_NONE, /* UI action has no copy/paste effect */ + CLIPUI_IMPLICIT, /* use the default clipboard implicit in mouse actions */ + CLIPUI_EXPLICIT, /* use the default clipboard for explicit Copy/Paste */ + CLIPUI_CUSTOM, /* use a named clipboard (on systems that support it) */ +}; + /* * Miscellaneous exports from the platform-specific code. * @@ -1507,6 +1590,7 @@ typedef void (*toplevel_callback_fn_t)(void *ctx); void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx); void run_toplevel_callbacks(void); int toplevel_callback_pending(void); +void delete_callbacks_for_context(void *ctx); typedef void (*toplevel_callback_notify_fn_t)(void *frontend); void request_callback_notifications(toplevel_callback_notify_fn_t notify, diff --git a/raw.c b/raw.c index 0c5445ad4..fbc9018d8 100644 --- a/raw.c +++ b/raw.c @@ -61,8 +61,8 @@ static void raw_check_close(Raw raw) } } -static int raw_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +static void raw_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) { Raw raw = (Raw) plug; @@ -92,17 +92,15 @@ static int raw_closing(Plug plug, const char *error_msg, int error_code, raw->sent_console_eof = TRUE; raw_check_close(raw); } - return 0; } -static int raw_receive(Plug plug, int urgent, char *data, int len) +static void raw_receive(Plug plug, int urgent, char *data, int len) { Raw raw = (Raw) plug; c_write(raw, data, len); /* We count 'session start', for proxy logging purposes, as being * when data is received from the network and printed. */ raw->session_started = TRUE; - return 1; } static void raw_sent(Plug plug, int bufsize) diff --git a/release.pl b/release.pl index cf73d9eb4..b5ad149c3 100755 --- a/release.pl +++ b/release.pl @@ -15,11 +15,13 @@ my $upload = 0; my $precheck = 0; my $postcheck = 0; +my $skip_ftp = 0; GetOptions("version=s" => \$version, "setver" => \$setver, "upload" => \$upload, "precheck" => \$precheck, - "postcheck" => \$postcheck) + "postcheck" => \$postcheck, + "no-ftp" => \$skip_ftp) or &usage(); # --set-version: construct a local commit which updates the version @@ -29,10 +31,12 @@ 0 == system "git", "diff-index", "--quiet", "--cached", "HEAD" or die "index is dirty"; 0 == system "git", "diff-files", "--quiet" or die "working tree is dirty"; - -f "Makefile" and die "run 'make distclean' first"; my $builddir = tempdir(DIR => ".", CLEANUP => 1); - 0 == system "./mkfiles.pl" or die; - 0 == system "cd $builddir && ../configure" or die; + 0 == system "git archive --format=tar HEAD | ( cd $builddir && tar xf - )" + or die; + 0 == system "cd $builddir && ./mkfiles.pl" or die; + 0 == system "cd $builddir && ./mkauto.sh" or die; + 0 == system "cd $builddir && ./configure" or die; 0 == system "cd $builddir && make pscp plink RELEASE=${version}" or die; our $pscp_transcript = `cd $builddir && ./pscp --help`; $pscp_transcript =~ s/^Unidentified build/Release ${version}/m or die; @@ -161,11 +165,13 @@ } # Now test-download the files themselves. - my $ftpdata = `curl -s $ftp_uri`; - printf " got %d bytes via FTP", length $ftpdata; - die "FTP download for $ftp_uri did not match" - if $ftpdata ne $real_content; - print ", ok\n"; + unless ($skip_ftp) { + my $ftpdata = `curl -s $ftp_uri`; + printf " got %d bytes via FTP", length $ftpdata; + die "FTP download for $ftp_uri did not match" + if $ftpdata ne $real_content; + print ", ok\n"; + } my $ua = LWP::UserAgent->new; my $httpresponse = $ua->get($http_uri); diff --git a/rlogin.c b/rlogin.c index eba468da2..ff2c0a8f4 100644 --- a/rlogin.c +++ b/rlogin.c @@ -53,8 +53,8 @@ static void rlogin_log(Plug plug, int type, SockAddr addr, int port, rlogin->conf, !rlogin->firstbyte); } -static int rlogin_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +static void rlogin_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) { Rlogin rlogin = (Rlogin) plug; @@ -76,10 +76,9 @@ static int rlogin_closing(Plug plug, const char *error_msg, int error_code, logevent(rlogin->frontend, error_msg); connection_fatal(rlogin->frontend, "%s", error_msg); } /* Otherwise, the remote side closed the connection normally. */ - return 0; } -static int rlogin_receive(Plug plug, int urgent, char *data, int len) +static void rlogin_receive(Plug plug, int urgent, char *data, int len) { Rlogin rlogin = (Rlogin) plug; if (urgent == 2) { @@ -113,7 +112,6 @@ static int rlogin_receive(Plug plug, int urgent, char *data, int len) if (len > 0) c_write(rlogin, data, len); } - return 1; } static void rlogin_sent(Plug plug, int bufsize) diff --git a/sessprep.c b/sessprep.c new file mode 100644 index 000000000..15d830d92 --- /dev/null +++ b/sessprep.c @@ -0,0 +1,84 @@ +/* + * sessprep.c: centralise some preprocessing done on Conf objects + * before launching them. + */ + +#include "putty.h" + +void prepare_session(Conf *conf) +{ + char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); + char *host = hostbuf; + char *p, *q; + + /* + * Trim leading whitespace from the hostname. + */ + host += strspn(host, " \t"); + + /* + * See if host is of the form user@host, and separate out the + * username if so. + */ + if (host[0] != '\0') { + /* + * Use strrchr, in case the _username_ in turn is of the form + * user@host, which has been known. + */ + char *atsign = strrchr(host, '@'); + if (atsign) { + *atsign = '\0'; + conf_set_str(conf, CONF_username, host); + host = atsign + 1; + } + } + + /* + * Trim a colon suffix off the hostname if it's there, and discard + * the text after it. + * + * The exact reason why we _ignore_ this text, rather than + * treating it as a port number, is unfortunately lost in the + * mists of history: the commit which originally introduced this + * change on 2001-05-06 was clear on _what_ it was doing but + * didn't bother to explain _why_. But I [SGT, 2017-12-03] suspect + * it has to do with priority order: what should a saved session + * do if its CONF_host contains 'server.example.com:123' and its + * CONF_port contains 456? If CONF_port contained the _default_ + * port number then it might be a good guess that the colon suffix + * on the host name was intended to override that, but you don't + * really want to get into making heuristic judgments on that + * basis. + * + * (Then again, you could just as easily make the same argument + * about whether a 'user@' prefix on the host name should override + * CONF_username, which this code _does_ do. I don't have a good + * answer, sadly. Both these pieces of behaviour have been around + * for years and it would probably cause subtle breakage in all + * sorts of long-forgotten scripting to go changing things around + * now.) + * + * In order to protect unbracketed IPv6 address literals against + * this treatment, we do not make this change at all if there's + * _more_ than one (un-IPv6-bracketed) colon. + */ + p = host_strchr(host, ':'); + if (p && p != host_strrchr(host, ':')) { + *p = '\0'; + } + + /* + * Remove any remaining whitespace. + */ + p = hostbuf; + q = host; + while (*q) { + if (*q != ' ' && *q != '\t') + *p++ = *q; + q++; + } + *p = '\0'; + + conf_set_str(conf, CONF_host, hostbuf); + sfree(hostbuf); +} diff --git a/settings.c b/settings.c index f810d3f97..a51f3d588 100644 --- a/settings.c +++ b/settings.c @@ -447,6 +447,54 @@ static void wprefs(void *sesskey, const char *name, sfree(buf); } +static void write_clip_setting(void *handle, const char *savekey, + Conf *conf, int confkey, int strconfkey) +{ + int val = conf_get_int(conf, confkey); + switch (val) { + case CLIPUI_NONE: + default: + write_setting_s(handle, savekey, "none"); + break; + case CLIPUI_IMPLICIT: + write_setting_s(handle, savekey, "implicit"); + break; + case CLIPUI_EXPLICIT: + write_setting_s(handle, savekey, "explicit"); + break; + case CLIPUI_CUSTOM: + { + char *sval = dupcat("custom:", conf_get_str(conf, strconfkey), + (const char *)NULL); + write_setting_s(handle, savekey, sval); + sfree(sval); + } + break; + } +} + +static void read_clip_setting(void *handle, const char *savekey, + int def, Conf *conf, int confkey, int strconfkey) +{ + char *setting = read_setting_s(handle, savekey); + int val; + + conf_set_str(conf, strconfkey, ""); + if (!setting) { + val = def; + } else if (!strcmp(setting, "implicit")) { + val = CLIPUI_IMPLICIT; + } else if (!strcmp(setting, "explicit")) { + val = CLIPUI_EXPLICIT; + } else if (!strncmp(setting, "custom:", 7)) { + val = CLIPUI_CUSTOM; + conf_set_str(conf, strconfkey, setting + 7); + } else { + val = CLIPUI_NONE; + } + conf_set_int(conf, confkey, val); +} + char *save_settings(const char *section, Conf *conf) { void *sesskey; @@ -609,6 +657,7 @@ void save_open_settings(void *sesskey, Conf *conf) write_setting_i(sesskey, "TryPalette", conf_get_int(conf, CONF_try_palette)); write_setting_i(sesskey, "ANSIColour", conf_get_int(conf, CONF_ansi_colour)); write_setting_i(sesskey, "Xterm256Colour", conf_get_int(conf, CONF_xterm_256_colour)); + write_setting_i(sesskey, "TrueColour", conf_get_int(conf, CONF_true_colour)); write_setting_i(sesskey, "BoldAsColour", conf_get_int(conf, CONF_bold_style)-1); for (i = 0; i < 22; i++) { @@ -637,6 +686,14 @@ void save_open_settings(void *sesskey, Conf *conf) } write_setting_s(sesskey, buf, buf2); } + write_setting_i(sesskey, "MouseAutocopy", + conf_get_int(conf, CONF_mouseautocopy)); + write_clip_setting(sesskey, "MousePaste", conf, + CONF_mousepaste, CONF_mousepaste_custom); + write_clip_setting(sesskey, "CtrlShiftIns", conf, + CONF_ctrlshiftins, CONF_ctrlshiftins_custom); + write_clip_setting(sesskey, "CtrlShiftCV", conf, + CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); write_setting_s(sesskey, "LineCodePage", conf_get_str(conf, CONF_line_codepage)); write_setting_i(sesskey, "CJKAmbigWide", conf_get_int(conf, CONF_cjk_ambig_wide)); write_setting_i(sesskey, "UTF8Override", conf_get_int(conf, CONF_utf8_override)); @@ -892,7 +949,7 @@ void load_open_settings(void *sesskey, Conf *conf) { /* SSH-2 only by default */ int sshprot = gppi_raw(sesskey, "SshProt", 3); - /* Old sessions may contain the values correponding to the fallbacks + /* Old sessions may contain the values corresponding to the fallbacks * we used to allow; migrate them */ if (sshprot == 1) sshprot = 0; /* => "SSH-1 only" */ else if (sshprot == 2) sshprot = 3; /* => "SSH-2 only" */ @@ -1005,6 +1062,7 @@ void load_open_settings(void *sesskey, Conf *conf) gppi(sesskey, "TryPalette", 0, conf, CONF_try_palette); gppi(sesskey, "ANSIColour", 1, conf, CONF_ansi_colour); gppi(sesskey, "Xterm256Colour", 1, conf, CONF_xterm_256_colour); + gppi(sesskey, "TrueColour", 1, conf, CONF_true_colour); i = gppi_raw(sesskey, "BoldAsColour", 1); conf_set_int(conf, CONF_bold_style, i+1); for (i = 0; i < 22; i++) { @@ -1057,6 +1115,14 @@ void load_open_settings(void *sesskey, Conf *conf) } sfree(buf2); } + gppi(sesskey, "MouseAutocopy", CLIPUI_DEFAULT_AUTOCOPY, + conf, CONF_mouseautocopy); + read_clip_setting(sesskey, "MousePaste", CLIPUI_DEFAULT_MOUSE, + conf, CONF_mousepaste, CONF_mousepaste_custom); + read_clip_setting(sesskey, "CtrlShiftIns", CLIPUI_DEFAULT_INS, + conf, CONF_ctrlshiftins, CONF_ctrlshiftins_custom); + read_clip_setting(sesskey, "CtrlShiftCV", CLIPUI_NONE, + conf, CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); /* * The empty default for LineCodePage will be converted later * into a plausible default for the locale. diff --git a/sign.sh b/sign.sh index bdf6245ff..8dbdb6135 100755 --- a/sign.sh +++ b/sign.sh @@ -10,11 +10,27 @@ set -e keyname=EEF20295D15F7E8A +preliminary=false -if test "x$1" = "x-r"; then - shift - keyname=9DFE2648B43434E4 -fi +while :; do + case "$1" in + -r) + shift + keyname=9DFE2648B43434E4 + ;; + -p) + shift + preliminary=true + ;; + -*) + echo "Unknown option '$1'" >&2 + exit 1 + ;; + *) + break + ;; + esac +done sign() { # Check for the prior existence of the signature, so we can @@ -27,9 +43,16 @@ sign() { cd "$1" echo "===== Signing with key '$keyname'" -for i in putty*src.zip putty*.tar.gz w32/*.exe w32/*.zip w32/*.msi w64/*.exe w64/*.zip w64/*.msi w32old/*.exe w32old/*.zip; do - sign --detach-sign "$i" "$i.gpg" -done -for i in md5sums sha1sums sha256sums sha512sums; do - sign --clearsign "$i" "$i.gpg" -done +if $preliminary; then + sign --clearsign sha512sums ../sha512sums-preliminary.gpg +else + for i in putty*src.zip putty*.tar.gz \ + w32/*.exe w32/*.zip w32/*.msi \ + w64/*.exe w64/*.zip w64/*.msi \ + w32old/*.exe w32old/*.zip; do + sign --detach-sign "$i" "$i.gpg" + done + for i in md5sums sha1sums sha256sums sha512sums; do + sign --clearsign "$i" "$i.gpg" + done +fi diff --git a/ssh.c b/ssh.c index 693f52d8d..9fcfe9e3e 100644 --- a/ssh.c +++ b/ssh.c @@ -303,7 +303,7 @@ enum { * macros look impenetrable to you, you might find it helpful to * read * - * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + * https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html * * which explains the theory behind these macros. * @@ -991,6 +991,8 @@ struct ssh_tag { * agent-forwarding channels live in their channel structure.) */ agent_pending_query *auth_agent_query; + + int need_random_unref; }; static const char *ssh_pkt_type(Ssh ssh, int type) @@ -1351,7 +1353,6 @@ static void ssh1_log_outgoing_packet(Ssh ssh, struct Packet *pkt) /* * Collect incoming data in the incoming packet buffer. * Decipher and verify the packet when it is completely read. - * Drop SSH1_MSG_DEBUG and SSH1_MSG_IGNORE packets. * Update the *data and *datalen variables. * Return a Packet structure when a packet is completed. */ @@ -3560,8 +3561,8 @@ void ssh_connshare_log(Ssh ssh, int event, const char *logtext, } } -static int ssh_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +static void ssh_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) { Ssh ssh = (Ssh) plug; int need_notify = ssh_do_close(ssh, FALSE); @@ -3583,18 +3584,15 @@ static int ssh_closing(Plug plug, const char *error_msg, int error_code, logevent(error_msg); if (!ssh->close_expected || !ssh->clean_exit) connection_fatal(ssh->frontend, "%s", error_msg); - return 0; } -static int ssh_receive(Plug plug, int urgent, char *data, int len) +static void ssh_receive(Plug plug, int urgent, char *data, int len) { Ssh ssh = (Ssh) plug; ssh_gotdata(ssh, (unsigned char *)data, len); if (ssh->state == SSH_STATE_CLOSED) { ssh_do_close(ssh, TRUE); - return 0; } - return 1; } static void ssh_sent(Plug plug, int bufsize) @@ -7788,6 +7786,8 @@ static int ssh2_try_send(struct ssh_channel *c) ssh2_pkt_addstring_start(pktout); ssh2_pkt_addstring_data(pktout, data, len); ssh2_pkt_send(ssh, pktout); + if (!ssh->s) /* a network error might have closed the socket */ + break; bufchain_consume(&c->v.v2.outbuffer, len); c->v.v2.remwindow -= len; } @@ -8108,6 +8108,8 @@ static void ssh2_msg_channel_response(Ssh ssh, struct Packet *pktin) return; } ocr->handler(c, pktin, ocr->ctx); + if (ssh->state == SSH_STATE_CLOSED) + return; /* in case the handler called bomb_out(), which some can */ c->v.v2.chanreq_head = ocr->next; sfree(ocr); /* @@ -8499,18 +8501,37 @@ static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) ssh_channel_try_eof(c); /* in case we had a pending EOF */ } -static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) +static char *ssh2_channel_open_failure_error_text(struct Packet *pktin) { static const char *const reasons[] = { - "", - "Administratively prohibited", - "Connect failed", - "Unknown channel type", - "Resource shortage", + NULL, + "Administratively prohibited", + "Connect failed", + "Unknown channel type", + "Resource shortage", }; unsigned reason_code; + const char *reason_code_string; + char reason_code_buf[256]; char *reason_string; int reason_length; + + reason_code = ssh_pkt_getuint32(pktin); + if (reason_code < lenof(reasons) && reasons[reason_code]) { + reason_code_string = reasons[reason_code]; + } else { + reason_code_string = reason_code_buf; + sprintf(reason_code_buf, "unknown reason code %#x", reason_code); + } + + ssh_pkt_getstring(pktin, &reason_string, &reason_length); + + return dupprintf("%s [%.*s]", reason_code_string, + reason_length, NULLTOEMPTY(reason_string)); +} + +static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) +{ struct ssh_channel *c; c = ssh_channel_msg(ssh, pktin); @@ -8519,14 +8540,9 @@ static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) assert(c->halfopen); /* ssh_channel_msg will have enforced this */ if (c->type == CHAN_SOCKDATA) { - reason_code = ssh_pkt_getuint32(pktin); - if (reason_code >= lenof(reasons)) - reason_code = 0; /* ensure reasons[reason_code] in range */ - ssh_pkt_getstring(pktin, &reason_string, &reason_length); - logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]", - reasons[reason_code], reason_length, - NULLTOEMPTY(reason_string)); - + char *errtext = ssh2_channel_open_failure_error_text(pktin); + logeventf(ssh, "Forwarded connection refused by server: %s", errtext); + sfree(errtext); pfd_close(c->u.pfd.pf); } else if (c->type == CHAN_ZOMBIE) { /* @@ -10711,6 +10727,7 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, } else { ssh->mainchan = snew(struct ssh_channel); ssh->mainchan->ssh = ssh; + ssh->mainchan->type = CHAN_MAINSESSION; ssh_channel_init(ssh->mainchan); if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) { @@ -10730,18 +10747,26 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, ssh->ncmode = FALSE; } crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { - bombout(("Server refused to open channel")); + if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION && + pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE) { + bombout(("Server sent strange packet %d in response to main " + "channel open request", pktin->type)); crStopV; - /* FIXME: error data comes back in FAILURE packet */ - } + } if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) { - bombout(("Server's channel confirmation cited wrong channel")); + bombout(("Server's response to main channel open cited wrong" + " channel number")); crStopV; } + if (pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE) { + char *errtext = ssh2_channel_open_failure_error_text(pktin); + bombout(("Server refused to open main channel: %s", errtext)); + sfree(errtext); + crStopV; + } + ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin); ssh->mainchan->halfopen = FALSE; - ssh->mainchan->type = CHAN_MAINSESSION; ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin); ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); update_specials_menu(ssh->frontend); @@ -11293,9 +11318,15 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, #endif random_ref(); /* do this now - may be needed by sharing setup code */ + ssh->need_random_unref = TRUE; p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive); if (p != NULL) { + /* Call random_unref now instead of waiting until the caller + * frees this useless Ssh object, in case the caller is + * impatient and just exits without bothering, in which case + * the random seed won't be re-saved. */ + ssh->need_random_unref = FALSE; random_unref(); return p; } @@ -11309,6 +11340,7 @@ static void ssh_free(void *handle) struct ssh_channel *c; struct ssh_rportfwd *pf; struct X11FakeAuth *auth; + int need_random_unref; if (ssh->v1_cipher_ctx) ssh->cipher->free_context(ssh->v1_cipher_ctx); @@ -11411,9 +11443,11 @@ static void ssh_free(void *handle) if (ssh->gsslibs) ssh_gss_cleanup(ssh->gsslibs); #endif + need_random_unref = ssh->need_random_unref; sfree(ssh); - random_unref(); + if (need_random_unref) + random_unref(); } /* diff --git a/sshaes.c b/sshaes.c index 904cbdb2b..508799135 100644 --- a/sshaes.c +++ b/sshaes.c @@ -1,5 +1,5 @@ /* - * aes.c - implementation of AES / Rijndael + * sshaes.c - implementation of AES / Rijndael * * AES is a flexible algorithm as regards endianness: it has no * inherent preference as to which way round you should form words @@ -33,22 +33,60 @@ #include "ssh.h" #define MAX_NR 14 /* max no of rounds */ -#define MAX_NK 8 /* max no of words in input key */ -#define MAX_NB 8 /* max no of words in cipher blk */ +#define NB 4 /* no of words in cipher blk */ #define mulby2(x) ( ((x&0x7F) << 1) ^ (x & 0x80 ? 0x1B : 0) ) +/* + * Select appropriate inline keyword for the compiler + */ +#if defined __GNUC__ || defined __clang__ +# define INLINE __inline__ +#elif defined (_MSC_VER) +# define INLINE __forceinline +#else +# define INLINE +#endif + typedef struct AESContext AESContext; struct AESContext { - word32 keysched[(MAX_NR + 1) * MAX_NB]; - word32 invkeysched[(MAX_NR + 1) * MAX_NB]; - void (*encrypt) (AESContext * ctx, word32 * block); - void (*decrypt) (AESContext * ctx, word32 * block); - word32 iv[MAX_NB]; - int Nb, Nr; + word32 keysched_buf[(MAX_NR + 1) * NB + 3]; + word32 invkeysched_buf[(MAX_NR + 1) * NB + 3]; + word32 *keysched, *invkeysched; + word32 iv[NB]; + int Nr; /* number of rounds */ + void (*encrypt_cbc)(unsigned char*, int, AESContext*); + void (*decrypt_cbc)(unsigned char*, int, AESContext*); + void (*sdctr)(unsigned char*, int, AESContext*); + int isNI; }; +static void aes_encrypt_cbc_sw(unsigned char*, int, AESContext*); +static void aes_decrypt_cbc_sw(unsigned char*, int, AESContext*); +static void aes_sdctr_sw(unsigned char*, int, AESContext*); + +INLINE static int supports_aes_ni(); +static void aes_setup_ni(AESContext * ctx, unsigned char *key, int keylen); + +INLINE static void aes_encrypt_cbc(unsigned char *blk, int len, AESContext * ctx) +{ + ctx->encrypt_cbc(blk, len, ctx); +} + +INLINE static void aes_decrypt_cbc(unsigned char *blk, int len, AESContext * ctx) +{ + ctx->decrypt_cbc(blk, len, ctx); +} + +INLINE static void aes_sdctr(unsigned char *blk, int len, AESContext * ctx) +{ + ctx->sdctr(blk, len, ctx); +} + +/* + * SW AES lookup tables + */ static const unsigned char Sbox[256] = { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, @@ -649,313 +687,43 @@ static const word32 D3[256] = { }; /* - * Common macros in both the encryption and decryption routines. - */ -#define ADD_ROUND_KEY_4 (block[0]^=*keysched++, block[1]^=*keysched++, \ - block[2]^=*keysched++, block[3]^=*keysched++) -#define ADD_ROUND_KEY_6 (block[0]^=*keysched++, block[1]^=*keysched++, \ - block[2]^=*keysched++, block[3]^=*keysched++, \ - block[4]^=*keysched++, block[5]^=*keysched++) -#define ADD_ROUND_KEY_8 (block[0]^=*keysched++, block[1]^=*keysched++, \ - block[2]^=*keysched++, block[3]^=*keysched++, \ - block[4]^=*keysched++, block[5]^=*keysched++, \ - block[6]^=*keysched++, block[7]^=*keysched++) -#define MOVEWORD(i) ( block[i] = newstate[i] ) - -/* - * Macros for the encryption routine. There are three encryption - * cores, for Nb=4,6,8. - */ -#define MAKEWORD(i) ( newstate[i] = (E0[(block[i] >> 24) & 0xFF] ^ \ - E1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \ - E2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \ - E3[block[(i+C3)%Nb] & 0xFF]) ) -#define LASTWORD(i) ( newstate[i] = (Sbox[(block[i] >> 24) & 0xFF] << 24) | \ - (Sbox[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \ - (Sbox[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \ - (Sbox[(block[(i+C3)%Nb] ) & 0xFF] ) ) - -/* - * Core encrypt routines, expecting word32 inputs read big-endian - * from the byte-oriented input stream. + * Set up an AESContext. `keylen' is measured in + * bytes; it can be either 16 (128-bit), 24 (192-bit), or 32 + * (256-bit). */ -static void aes_encrypt_nb_4(AESContext * ctx, word32 * block) -{ - int i; - static const int C1 = 1, C2 = 2, C3 = 3, Nb = 4; - word32 *keysched = ctx->keysched; - word32 newstate[4]; - for (i = 0; i < ctx->Nr - 1; i++) { - ADD_ROUND_KEY_4; - MAKEWORD(0); - MAKEWORD(1); - MAKEWORD(2); - MAKEWORD(3); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - } - ADD_ROUND_KEY_4; - LASTWORD(0); - LASTWORD(1); - LASTWORD(2); - LASTWORD(3); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - ADD_ROUND_KEY_4; -} -static void aes_encrypt_nb_6(AESContext * ctx, word32 * block) +static void aes_setup(AESContext * ctx, unsigned char *key, int keylen) { - int i; - static const int C1 = 1, C2 = 2, C3 = 3, Nb = 6; - word32 *keysched = ctx->keysched; - word32 newstate[6]; - for (i = 0; i < ctx->Nr - 1; i++) { - ADD_ROUND_KEY_6; - MAKEWORD(0); - MAKEWORD(1); - MAKEWORD(2); - MAKEWORD(3); - MAKEWORD(4); - MAKEWORD(5); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - } - ADD_ROUND_KEY_6; - LASTWORD(0); - LASTWORD(1); - LASTWORD(2); - LASTWORD(3); - LASTWORD(4); - LASTWORD(5); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - ADD_ROUND_KEY_6; -} -static void aes_encrypt_nb_8(AESContext * ctx, word32 * block) -{ - int i; - static const int C1 = 1, C2 = 3, C3 = 4, Nb = 8; - word32 *keysched = ctx->keysched; - word32 newstate[8]; - for (i = 0; i < ctx->Nr - 1; i++) { - ADD_ROUND_KEY_8; - MAKEWORD(0); - MAKEWORD(1); - MAKEWORD(2); - MAKEWORD(3); - MAKEWORD(4); - MAKEWORD(5); - MAKEWORD(6); - MAKEWORD(7); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - MOVEWORD(6); - MOVEWORD(7); - } - ADD_ROUND_KEY_8; - LASTWORD(0); - LASTWORD(1); - LASTWORD(2); - LASTWORD(3); - LASTWORD(4); - LASTWORD(5); - LASTWORD(6); - LASTWORD(7); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - MOVEWORD(6); - MOVEWORD(7); - ADD_ROUND_KEY_8; -} - -#undef MAKEWORD -#undef LASTWORD - -/* - * Macros for the decryption routine. There are three decryption - * cores, for Nb=4,6,8. - */ -#define MAKEWORD(i) ( newstate[i] = (D0[(block[i] >> 24) & 0xFF] ^ \ - D1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \ - D2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \ - D3[block[(i+C3)%Nb] & 0xFF]) ) -#define LASTWORD(i) (newstate[i] = (Sboxinv[(block[i] >> 24) & 0xFF] << 24) | \ - (Sboxinv[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \ - (Sboxinv[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \ - (Sboxinv[(block[(i+C3)%Nb] ) & 0xFF] ) ) + int i, j, Nk, rconst; + size_t bufaddr; -/* - * Core decrypt routines, expecting word32 inputs read big-endian - * from the byte-oriented input stream. - */ -static void aes_decrypt_nb_4(AESContext * ctx, word32 * block) -{ - int i; - static const int C1 = 4 - 1, C2 = 4 - 2, C3 = 4 - 3, Nb = 4; - word32 *keysched = ctx->invkeysched; - word32 newstate[4]; - for (i = 0; i < ctx->Nr - 1; i++) { - ADD_ROUND_KEY_4; - MAKEWORD(0); - MAKEWORD(1); - MAKEWORD(2); - MAKEWORD(3); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - } - ADD_ROUND_KEY_4; - LASTWORD(0); - LASTWORD(1); - LASTWORD(2); - LASTWORD(3); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - ADD_ROUND_KEY_4; -} -static void aes_decrypt_nb_6(AESContext * ctx, word32 * block) -{ - int i; - static const int C1 = 6 - 1, C2 = 6 - 2, C3 = 6 - 3, Nb = 6; - word32 *keysched = ctx->invkeysched; - word32 newstate[6]; - for (i = 0; i < ctx->Nr - 1; i++) { - ADD_ROUND_KEY_6; - MAKEWORD(0); - MAKEWORD(1); - MAKEWORD(2); - MAKEWORD(3); - MAKEWORD(4); - MAKEWORD(5); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - } - ADD_ROUND_KEY_6; - LASTWORD(0); - LASTWORD(1); - LASTWORD(2); - LASTWORD(3); - LASTWORD(4); - LASTWORD(5); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - ADD_ROUND_KEY_6; -} -static void aes_decrypt_nb_8(AESContext * ctx, word32 * block) -{ - int i; - static const int C1 = 8 - 1, C2 = 8 - 3, C3 = 8 - 4, Nb = 8; - word32 *keysched = ctx->invkeysched; - word32 newstate[8]; - for (i = 0; i < ctx->Nr - 1; i++) { - ADD_ROUND_KEY_8; - MAKEWORD(0); - MAKEWORD(1); - MAKEWORD(2); - MAKEWORD(3); - MAKEWORD(4); - MAKEWORD(5); - MAKEWORD(6); - MAKEWORD(7); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - MOVEWORD(6); - MOVEWORD(7); - } - ADD_ROUND_KEY_8; - LASTWORD(0); - LASTWORD(1); - LASTWORD(2); - LASTWORD(3); - LASTWORD(4); - LASTWORD(5); - LASTWORD(6); - LASTWORD(7); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - MOVEWORD(6); - MOVEWORD(7); - ADD_ROUND_KEY_8; -} + ctx->Nr = 6 + (keylen / 4); /* Number of rounds */ -#undef MAKEWORD -#undef LASTWORD + /* Ensure the key schedule arrays are 16-byte aligned */ + bufaddr = (size_t)ctx->keysched_buf; + ctx->keysched = ctx->keysched_buf + + (0xF & -bufaddr) / sizeof(word32); + assert((size_t)ctx->keysched % 16 == 0); + bufaddr = (size_t)ctx->invkeysched_buf; + ctx->invkeysched = ctx->invkeysched_buf + + (0xF & -bufaddr) / sizeof(word32); + assert((size_t)ctx->invkeysched % 16 == 0); + ctx->isNI = supports_aes_ni(); -/* - * Set up an AESContext. `keylen' and `blocklen' are measured in - * bytes; each can be either 16 (128-bit), 24 (192-bit), or 32 - * (256-bit). - */ -static void aes_setup(AESContext * ctx, int blocklen, - unsigned char *key, int keylen) -{ - int i, j, Nk, rconst; + if (ctx->isNI) { + aes_setup_ni(ctx, key, keylen); + return; + } - assert(blocklen == 16 || blocklen == 24 || blocklen == 32); assert(keylen == 16 || keylen == 24 || keylen == 32); - /* - * Basic parameters. Words per block, words in key, rounds. - */ - Nk = keylen / 4; - ctx->Nb = blocklen / 4; - ctx->Nr = 6 + (ctx->Nb > Nk ? ctx->Nb : Nk); - - /* - * Assign core-function pointers. - */ - if (ctx->Nb == 8) - ctx->encrypt = aes_encrypt_nb_8, ctx->decrypt = aes_decrypt_nb_8; - else if (ctx->Nb == 6) - ctx->encrypt = aes_encrypt_nb_6, ctx->decrypt = aes_decrypt_nb_6; - else if (ctx->Nb == 4) - ctx->encrypt = aes_encrypt_nb_4, ctx->decrypt = aes_decrypt_nb_4; + ctx->encrypt_cbc = aes_encrypt_cbc_sw; + ctx->decrypt_cbc = aes_decrypt_cbc_sw; + ctx->sdctr = aes_sdctr_sw; - /* - * Now do the key setup itself. - */ + Nk = keylen / 4; rconst = 1; - for (i = 0; i < (ctx->Nr + 1) * ctx->Nb; i++) { + for (i = 0; i < (ctx->Nr + 1) * NB; i++) { if (i < Nk) ctx->keysched[i] = GET_32BIT_MSB_FIRST(key + 4 * i); else { @@ -990,9 +758,9 @@ static void aes_setup(AESContext * ctx, int blocklen, * Now prepare the modified keys for the inverse cipher. */ for (i = 0; i <= ctx->Nr; i++) { - for (j = 0; j < ctx->Nb; j++) { + for (j = 0; j < NB; j++) { word32 temp; - temp = ctx->keysched[(ctx->Nr - i) * ctx->Nb + j]; + temp = ctx->keysched[(ctx->Nr - i) * NB + j]; if (i != 0 && i != ctx->Nr) { /* * Perform the InvMixColumn operation on i. The D @@ -1010,88 +778,192 @@ static void aes_setup(AESContext * ctx, int blocklen, temp ^= D2[Sbox[c]]; temp ^= D3[Sbox[d]]; } - ctx->invkeysched[i * ctx->Nb + j] = temp; + ctx->invkeysched[i * NB + j] = temp; } } } -static void aes_encrypt(AESContext * ctx, word32 * block) -{ - ctx->encrypt(ctx, block); -} +/* + * Software encrypt/decrypt macros + */ +#define ADD_ROUND_KEY (block[0]^=*keysched++, \ + block[1]^=*keysched++, \ + block[2]^=*keysched++, \ + block[3]^=*keysched++) +#define MOVEWORD(i) ( block[i] = newstate[i] ) -static void aes_decrypt(AESContext * ctx, word32 * block) -{ - ctx->decrypt(ctx, block); -} +#define ENCWORD(i) ( newstate[i] = (E0[(block[i ] >> 24) & 0xFF] ^ \ + E1[(block[(i+1)%NB] >> 16) & 0xFF] ^ \ + E2[(block[(i+2)%NB] >> 8) & 0xFF] ^ \ + E3[ block[(i+3)%NB] & 0xFF]) ) +#define ENCROUND { ENCWORD(0); ENCWORD(1); ENCWORD(2); ENCWORD(3); \ + MOVEWORD(0); MOVEWORD(1); MOVEWORD(2); MOVEWORD(3); ADD_ROUND_KEY; } -static void aes_encrypt_cbc(unsigned char *blk, int len, AESContext * ctx) +#define ENCLASTWORD(i) ( newstate[i] = \ + (Sbox[(block[i] >> 24) & 0xFF] << 24) | \ + (Sbox[(block[(i+1)%NB] >> 16) & 0xFF] << 16) | \ + (Sbox[(block[(i+2)%NB] >> 8) & 0xFF] << 8) | \ + (Sbox[(block[(i+3)%NB] ) & 0xFF] ) ) +#define ENCLASTROUND { ENCLASTWORD(0); ENCLASTWORD(1); ENCLASTWORD(2); ENCLASTWORD(3); \ + MOVEWORD(0); MOVEWORD(1); MOVEWORD(2); MOVEWORD(3); ADD_ROUND_KEY; } + +#define DECWORD(i) ( newstate[i] = (D0[(block[i] >> 24) & 0xFF] ^ \ + D1[(block[(i+3)%NB] >> 16) & 0xFF] ^ \ + D2[(block[(i+2)%NB] >> 8) & 0xFF] ^ \ + D3[ block[(i+1)%NB] & 0xFF]) ) +#define DECROUND { DECWORD(0); DECWORD(1); DECWORD(2); DECWORD(3); \ + MOVEWORD(0); MOVEWORD(1); MOVEWORD(2); MOVEWORD(3); ADD_ROUND_KEY; } + +#define DECLASTWORD(i) (newstate[i] = \ + (Sboxinv[(block[i] >> 24) & 0xFF] << 24) | \ + (Sboxinv[(block[(i+3)%NB] >> 16) & 0xFF] << 16) | \ + (Sboxinv[(block[(i+2)%NB] >> 8) & 0xFF] << 8) | \ + (Sboxinv[(block[(i+1)%NB] ) & 0xFF] ) ) +#define DECLASTROUND { DECLASTWORD(0); DECLASTWORD(1); DECLASTWORD(2); DECLASTWORD(3); \ + MOVEWORD(0); MOVEWORD(1); MOVEWORD(2); MOVEWORD(3); ADD_ROUND_KEY; } + +/* + * Software AES encrypt/decrypt core + */ +static void aes_encrypt_cbc_sw(unsigned char *blk, int len, AESContext * ctx) { - word32 iv[4]; + word32 block[4]; + unsigned char* finish = blk + len; int i; assert((len & 15) == 0); - memcpy(iv, ctx->iv, sizeof(iv)); + memcpy(block, ctx->iv, sizeof(block)); - while (len > 0) { + while (blk < finish) { + word32 *keysched = ctx->keysched; + word32 newstate[4]; for (i = 0; i < 4; i++) - iv[i] ^= GET_32BIT_MSB_FIRST(blk + 4 * i); - aes_encrypt(ctx, iv); + block[i] ^= GET_32BIT_MSB_FIRST(blk + 4 * i); + ADD_ROUND_KEY; + switch (ctx->Nr) { + case 14: + ENCROUND; + ENCROUND; + case 12: + ENCROUND; + ENCROUND; + case 10: + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCLASTROUND; + break; + default: + assert(0); + } for (i = 0; i < 4; i++) - PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i]); + PUT_32BIT_MSB_FIRST(blk + 4 * i, block[i]); blk += 16; - len -= 16; } - memcpy(ctx->iv, iv, sizeof(iv)); + memcpy(ctx->iv, block, sizeof(block)); } -static void aes_decrypt_cbc(unsigned char *blk, int len, AESContext * ctx) +static void aes_sdctr_sw(unsigned char *blk, int len, AESContext *ctx) { - word32 iv[4], x[4], ct[4]; + word32 iv[4]; + unsigned char* finish = blk + len; int i; assert((len & 15) == 0); memcpy(iv, ctx->iv, sizeof(iv)); - while (len > 0) { - for (i = 0; i < 4; i++) - x[i] = ct[i] = GET_32BIT_MSB_FIRST(blk + 4 * i); - aes_decrypt(ctx, x); + while (blk < finish) { + word32 *keysched = ctx->keysched; + word32 newstate[4], block[4], tmp; + memcpy(block, iv, sizeof(block)); + ADD_ROUND_KEY; + switch (ctx->Nr) { + case 14: + ENCROUND; + ENCROUND; + case 12: + ENCROUND; + ENCROUND; + case 10: + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCLASTROUND; + break; + default: + assert(0); + } for (i = 0; i < 4; i++) { - PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i] ^ x[i]); - iv[i] = ct[i]; + tmp = GET_32BIT_MSB_FIRST(blk + 4 * i); + PUT_32BIT_MSB_FIRST(blk + 4 * i, tmp ^ block[i]); } + for (i = 3; i >= 0; i--) + if ((iv[i] = (iv[i] + 1) & 0xffffffff) != 0) + break; blk += 16; - len -= 16; } memcpy(ctx->iv, iv, sizeof(iv)); } -static void aes_sdctr(unsigned char *blk, int len, AESContext *ctx) +static void aes_decrypt_cbc_sw(unsigned char *blk, int len, AESContext * ctx) { - word32 iv[4], b[4], tmp; + word32 iv[4]; + unsigned char* finish = blk + len; int i; assert((len & 15) == 0); memcpy(iv, ctx->iv, sizeof(iv)); - while (len > 0) { - memcpy(b, iv, sizeof(b)); - aes_encrypt(ctx, b); + while (blk < finish) { + word32 *keysched = ctx->invkeysched; + word32 newstate[4], ct[4], block[4]; + for (i = 0; i < 4; i++) + block[i] = ct[i] = GET_32BIT_MSB_FIRST(blk + 4 * i); + ADD_ROUND_KEY; + switch (ctx->Nr) { + case 14: + DECROUND; + DECROUND; + case 12: + DECROUND; + DECROUND; + case 10: + DECROUND; + DECROUND; + DECROUND; + DECROUND; + DECROUND; + DECROUND; + DECROUND; + DECROUND; + DECROUND; + DECLASTROUND; + break; + default: + assert(0); + } for (i = 0; i < 4; i++) { - tmp = GET_32BIT_MSB_FIRST(blk + 4 * i); - PUT_32BIT_MSB_FIRST(blk + 4 * i, tmp ^ b[i]); + PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i] ^ block[i]); + iv[i] = ct[i]; } - for (i = 3; i >= 0; i--) - if ((iv[i] = (iv[i] + 1) & 0xffffffff) != 0) - break; blk += 16; - len -= 16; } memcpy(ctx->iv, iv, sizeof(iv)); @@ -1110,27 +982,32 @@ void aes_free_context(void *handle) void aes128_key(void *handle, unsigned char *key) { AESContext *ctx = (AESContext *)handle; - aes_setup(ctx, 16, key, 16); + aes_setup(ctx, key, 16); } void aes192_key(void *handle, unsigned char *key) { AESContext *ctx = (AESContext *)handle; - aes_setup(ctx, 16, key, 24); + aes_setup(ctx, key, 24); } void aes256_key(void *handle, unsigned char *key) { AESContext *ctx = (AESContext *)handle; - aes_setup(ctx, 16, key, 32); + aes_setup(ctx, key, 32); } void aes_iv(void *handle, unsigned char *iv) { AESContext *ctx = (AESContext *)handle; - int i; - for (i = 0; i < 4; i++) - ctx->iv[i] = GET_32BIT_MSB_FIRST(iv + 4 * i); + if (ctx->isNI) { + memcpy(ctx->iv, iv, sizeof(ctx->iv)); + } + else { + int i; + for (i = 0; i < 4; i++) + ctx->iv[i] = GET_32BIT_MSB_FIRST(iv + 4 * i); + } } void aes_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len) @@ -1154,7 +1031,7 @@ static void aes_ssh2_sdctr(void *handle, unsigned char *blk, int len) void aes256_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len) { AESContext ctx; - aes_setup(&ctx, 16, key, 32); + aes_setup(&ctx, key, 32); memset(ctx.iv, 0, sizeof(ctx.iv)); aes_encrypt_cbc(blk, len, &ctx); smemclr(&ctx, sizeof(ctx)); @@ -1163,7 +1040,7 @@ void aes256_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len) void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len) { AESContext ctx; - aes_setup(&ctx, 16, key, 32); + aes_setup(&ctx, key, 32); memset(ctx.iv, 0, sizeof(ctx.iv)); aes_decrypt_cbc(blk, len, &ctx); smemclr(&ctx, sizeof(ctx)); @@ -1239,3 +1116,610 @@ const struct ssh2_ciphers ssh2_aes = { sizeof(aes_list) / sizeof(*aes_list), aes_list }; + +/* + * Implementation of AES for PuTTY using AES-NI + * instuction set expansion was made by: + * @author Pavel Kryukov + * @author Maxim Kuznetsov + * @author Svyatoslav Kuzmich + * + * For Putty AES NI project + * http://pavelkryukov.github.io/putty-aes-ni/ + */ + +/* + * Check of compiler version + */ +#ifdef _FORCE_AES_NI +# define COMPILER_SUPPORTS_AES_NI +#elif defined(__clang__) +# if (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 8)) && (defined(__x86_64__) || defined(__i386)) +# define COMPILER_SUPPORTS_AES_NI +# endif +#elif defined(__GNUC__) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) && (defined(__x86_64__) || defined(__i386)) +# define COMPILER_SUPPORTS_AES_NI +# endif +#elif defined (_MSC_VER) +# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 +# define COMPILER_SUPPORTS_AES_NI +# endif +#endif + +#ifdef _FORCE_SOFTWARE_AES +# undef COMPILER_SUPPORTS_AES_NI +#endif + +#if defined(__clang__) +# if !__has_attribute(target) +/* If clang is old enough not to support __attribute__((target(...))) + * as used below, then we can't use this code after all. */ +# undef COMPILER_SUPPORTS_AES_NI +# endif +#endif + +#ifdef COMPILER_SUPPORTS_AES_NI + +/* + * Set target architecture for Clang and GCC + */ +#if !defined(__clang__) && defined(__GNUC__) +# pragma GCC target("aes") +# pragma GCC target("sse4.1") +#endif + +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) +# define FUNC_ISA __attribute__ ((target("sse4.1,aes"))) +#else +# define FUNC_ISA +#endif + +#include +#include + +/* + * Determinators of CPU type + */ +#if defined(__clang__) || defined(__GNUC__) + +#include +INLINE static int supports_aes_ni() +{ + unsigned int CPUInfo[4]; + __cpuid(1, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]); + return (CPUInfo[2] & (1 << 25)) && (CPUInfo[2] & (1 << 19)); /* Check AES and SSE4.1 */ +} + +#else /* defined(__clang__) || defined(__GNUC__) */ + +INLINE static int supports_aes_ni() +{ + unsigned int CPUInfo[4]; + __cpuid(CPUInfo, 1); + return (CPUInfo[2] & (1 << 25)) && (CPUInfo[2] & (1 << 19)); /* Check AES and SSE4.1 */ +} + +#endif /* defined(__clang__) || defined(__GNUC__) */ + +/* + * Wrapper of SHUFPD instruction for MSVC + */ +#ifdef _MSC_VER +INLINE static __m128i mm_shuffle_pd_i0(__m128i a, __m128i b) +{ + union { + __m128i i; + __m128d d; + } au, bu, ru; + au.i = a; + bu.i = b; + ru.d = _mm_shuffle_pd(au.d, bu.d, 0); + return ru.i; +} + +INLINE static __m128i mm_shuffle_pd_i1(__m128i a, __m128i b) +{ + union { + __m128i i; + __m128d d; + } au, bu, ru; + au.i = a; + bu.i = b; + ru.d = _mm_shuffle_pd(au.d, bu.d, 1); + return ru.i; +} +#else +#define mm_shuffle_pd_i0(a, b) ((__m128i)_mm_shuffle_pd((__m128d)a, (__m128d)b, 0)); +#define mm_shuffle_pd_i1(a, b) ((__m128i)_mm_shuffle_pd((__m128d)a, (__m128d)b, 1)); +#endif + +/* + * AES-NI key expansion assist functions + */ +FUNC_ISA +INLINE static __m128i AES_128_ASSIST (__m128i temp1, __m128i temp2) +{ + __m128i temp3; + temp2 = _mm_shuffle_epi32 (temp2 ,0xff); + temp3 = _mm_slli_si128 (temp1, 0x4); + temp1 = _mm_xor_si128 (temp1, temp3); + temp3 = _mm_slli_si128 (temp3, 0x4); + temp1 = _mm_xor_si128 (temp1, temp3); + temp3 = _mm_slli_si128 (temp3, 0x4); + temp1 = _mm_xor_si128 (temp1, temp3); + temp1 = _mm_xor_si128 (temp1, temp2); + return temp1; +} + +FUNC_ISA +INLINE static void KEY_192_ASSIST(__m128i* temp1, __m128i * temp2, __m128i * temp3) +{ + __m128i temp4; + *temp2 = _mm_shuffle_epi32 (*temp2, 0x55); + temp4 = _mm_slli_si128 (*temp1, 0x4); + *temp1 = _mm_xor_si128 (*temp1, temp4); + temp4 = _mm_slli_si128 (temp4, 0x4); + *temp1 = _mm_xor_si128 (*temp1, temp4); + temp4 = _mm_slli_si128 (temp4, 0x4); + *temp1 = _mm_xor_si128 (*temp1, temp4); + *temp1 = _mm_xor_si128 (*temp1, *temp2); + *temp2 = _mm_shuffle_epi32(*temp1, 0xff); + temp4 = _mm_slli_si128 (*temp3, 0x4); + *temp3 = _mm_xor_si128 (*temp3, temp4); + *temp3 = _mm_xor_si128 (*temp3, *temp2); +} + +FUNC_ISA +INLINE static void KEY_256_ASSIST_1(__m128i* temp1, __m128i * temp2) +{ + __m128i temp4; + *temp2 = _mm_shuffle_epi32(*temp2, 0xff); + temp4 = _mm_slli_si128 (*temp1, 0x4); + *temp1 = _mm_xor_si128 (*temp1, temp4); + temp4 = _mm_slli_si128 (temp4, 0x4); + *temp1 = _mm_xor_si128 (*temp1, temp4); + temp4 = _mm_slli_si128 (temp4, 0x4); + *temp1 = _mm_xor_si128 (*temp1, temp4); + *temp1 = _mm_xor_si128 (*temp1, *temp2); +} + +FUNC_ISA +INLINE static void KEY_256_ASSIST_2(__m128i* temp1, __m128i * temp3) +{ + __m128i temp2,temp4; + temp4 = _mm_aeskeygenassist_si128 (*temp1, 0x0); + temp2 = _mm_shuffle_epi32(temp4, 0xaa); + temp4 = _mm_slli_si128 (*temp3, 0x4); + *temp3 = _mm_xor_si128 (*temp3, temp4); + temp4 = _mm_slli_si128 (temp4, 0x4); + *temp3 = _mm_xor_si128 (*temp3, temp4); + temp4 = _mm_slli_si128 (temp4, 0x4); + *temp3 = _mm_xor_si128 (*temp3, temp4); + *temp3 = _mm_xor_si128 (*temp3, temp2); +} + +/* + * AES-NI key expansion core + */ +FUNC_ISA +static void AES_128_Key_Expansion (unsigned char *userkey, __m128i *key) +{ + __m128i temp1, temp2; + temp1 = _mm_loadu_si128((__m128i*)userkey); + key[0] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1 ,0x1); + temp1 = AES_128_ASSIST(temp1, temp2); + key[1] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x2); + temp1 = AES_128_ASSIST(temp1, temp2); + key[2] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x4); + temp1 = AES_128_ASSIST(temp1, temp2); + key[3] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x8); + temp1 = AES_128_ASSIST(temp1, temp2); + key[4] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x10); + temp1 = AES_128_ASSIST(temp1, temp2); + key[5] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x20); + temp1 = AES_128_ASSIST(temp1, temp2); + key[6] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x40); + temp1 = AES_128_ASSIST(temp1, temp2); + key[7] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x80); + temp1 = AES_128_ASSIST(temp1, temp2); + key[8] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x1b); + temp1 = AES_128_ASSIST(temp1, temp2); + key[9] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x36); + temp1 = AES_128_ASSIST(temp1, temp2); + key[10] = temp1; +} + +FUNC_ISA +static void AES_192_Key_Expansion (unsigned char *userkey, __m128i *key) +{ + __m128i temp1, temp2, temp3; + temp1 = _mm_loadu_si128((__m128i*)userkey); + temp3 = _mm_loadu_si128((__m128i*)(userkey+16)); + key[0]=temp1; + key[1]=temp3; + temp2=_mm_aeskeygenassist_si128 (temp3,0x1); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[1] = mm_shuffle_pd_i0(key[1], temp1); + key[2] = mm_shuffle_pd_i1(temp1, temp3); + temp2=_mm_aeskeygenassist_si128 (temp3,0x2); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[3]=temp1; + key[4]=temp3; + temp2=_mm_aeskeygenassist_si128 (temp3,0x4); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[4] = mm_shuffle_pd_i0(key[4], temp1); + key[5] = mm_shuffle_pd_i1(temp1, temp3); + temp2=_mm_aeskeygenassist_si128 (temp3,0x8); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[6]=temp1; + key[7]=temp3; + temp2=_mm_aeskeygenassist_si128 (temp3,0x10); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[7] = mm_shuffle_pd_i0(key[7], temp1); + key[8] = mm_shuffle_pd_i1(temp1, temp3); + temp2=_mm_aeskeygenassist_si128 (temp3,0x20); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[9]=temp1; + key[10]=temp3; + temp2=_mm_aeskeygenassist_si128 (temp3,0x40); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[10] = mm_shuffle_pd_i0(key[10], temp1); + key[11] = mm_shuffle_pd_i1(temp1, temp3); + temp2=_mm_aeskeygenassist_si128 (temp3,0x80); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[12]=temp1; + key[13]=temp3; +} + +FUNC_ISA +static void AES_256_Key_Expansion (unsigned char *userkey, __m128i *key) +{ + __m128i temp1, temp2, temp3; + temp1 = _mm_loadu_si128((__m128i*)userkey); + temp3 = _mm_loadu_si128((__m128i*)(userkey+16)); + key[0] = temp1; + key[1] = temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x01); + KEY_256_ASSIST_1(&temp1, &temp2); + key[2]=temp1; + KEY_256_ASSIST_2(&temp1, &temp3); + key[3]=temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x02); + KEY_256_ASSIST_1(&temp1, &temp2); + key[4]=temp1; + KEY_256_ASSIST_2(&temp1, &temp3); + key[5]=temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x04); + KEY_256_ASSIST_1(&temp1, &temp2); + key[6]=temp1; + KEY_256_ASSIST_2(&temp1, &temp3); + key[7]=temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x08); + KEY_256_ASSIST_1(&temp1, &temp2); + key[8]=temp1; + KEY_256_ASSIST_2(&temp1, &temp3); + key[9]=temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x10); + KEY_256_ASSIST_1(&temp1, &temp2); + key[10]=temp1; + KEY_256_ASSIST_2(&temp1, &temp3); + key[11]=temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x20); + KEY_256_ASSIST_1(&temp1, &temp2); + key[12]=temp1; + KEY_256_ASSIST_2(&temp1, &temp3); + key[13]=temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x40); + KEY_256_ASSIST_1(&temp1, &temp2); + key[14]=temp1; +} + +/* + * AES-NI encrypt/decrypt core + */ +FUNC_ISA +static void aes_encrypt_cbc_ni(unsigned char *blk, int len, AESContext * ctx) +{ + __m128i enc; + __m128i* block = (__m128i*)blk; + const __m128i* finish = (__m128i*)(blk + len); + + assert((len & 15) == 0); + + /* Load IV */ + enc = _mm_loadu_si128((__m128i*)(ctx->iv)); + while (block < finish) { + /* Key schedule ptr */ + __m128i* keysched = (__m128i*)ctx->keysched; + + /* Xor data with IV */ + enc = _mm_xor_si128(_mm_loadu_si128(block), enc); + + /* Perform rounds */ + enc = _mm_xor_si128(enc, *keysched); + switch (ctx->Nr) { + case 14: + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + case 12: + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + case 10: + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenclast_si128(enc, *(++keysched)); + break; + default: + assert(0); + } + + /* Store and go to next block */ + _mm_storeu_si128(block, enc); + ++block; + } + + /* Update IV */ + _mm_storeu_si128((__m128i*)(ctx->iv), enc); +} + +FUNC_ISA +static void aes_decrypt_cbc_ni(unsigned char *blk, int len, AESContext * ctx) +{ + __m128i dec = _mm_setzero_si128(); + __m128i last, iv; + __m128i* block = (__m128i*)blk; + const __m128i* finish = (__m128i*)(blk + len); + + assert((len & 15) == 0); + + /* Load IV */ + iv = _mm_loadu_si128((__m128i*)(ctx->iv)); + while (block < finish) { + /* Key schedule ptr */ + __m128i* keysched = (__m128i*)ctx->invkeysched; + last = _mm_loadu_si128(block); + dec = _mm_xor_si128(last, *keysched); + switch (ctx->Nr) { + case 14: + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + case 12: + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + case 10: + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdeclast_si128(dec, *(++keysched)); + break; + default: + assert(0); + } + + /* Xor data with IV */ + dec = _mm_xor_si128(iv, dec); + + /* Store data */ + _mm_storeu_si128(block, dec); + iv = last; + + /* Go to next block */ + ++block; + } + + /* Update IV */ + _mm_storeu_si128((__m128i*)(ctx->iv), dec); +} + +FUNC_ISA +static void aes_sdctr_ni(unsigned char *blk, int len, AESContext *ctx) +{ + const __m128i BSWAP_EPI64 = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12); + const __m128i ONE = _mm_setr_epi32(0,0,0,1); + const __m128i ZERO = _mm_setzero_si128(); + __m128i iv; + __m128i* block = (__m128i*)blk; + const __m128i* finish = (__m128i*)(blk + len); + + assert((len & 15) == 0); + + iv = _mm_loadu_si128((__m128i*)ctx->iv); + + while (block < finish) { + __m128i enc; + __m128i* keysched = (__m128i*)ctx->keysched;/* Key schedule ptr */ + + /* Perform rounds */ + enc = _mm_xor_si128(iv, *keysched); /* Note that we use IV */ + switch (ctx->Nr) { + case 14: + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + case 12: + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + case 10: + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenclast_si128(enc, *(++keysched)); + break; + default: + assert(0); + } + + /* Xor with block and store result */ + enc = _mm_xor_si128(enc, _mm_loadu_si128(block)); + _mm_storeu_si128(block, enc); + + /* Increment of IV */ + iv = _mm_shuffle_epi8(iv, BSWAP_EPI64); /* Swap endianess */ + iv = _mm_add_epi64(iv, ONE); /* Inc low part */ + enc = _mm_cmpeq_epi64(iv, ZERO); /* Check for carry */ + enc = _mm_unpacklo_epi64(ZERO, enc); /* Pack carry reg */ + iv = _mm_sub_epi64(iv, enc); /* Sub carry reg */ + iv = _mm_shuffle_epi8(iv, BSWAP_EPI64); /* Swap enianess back */ + + /* Go to next block */ + ++block; + } + + /* Update IV */ + _mm_storeu_si128((__m128i*)ctx->iv, iv); +} + +FUNC_ISA +static void aes_inv_key_10(AESContext * ctx) +{ + __m128i* keysched = (__m128i*)ctx->keysched; + __m128i* invkeysched = (__m128i*)ctx->invkeysched; + + *(invkeysched + 10) = *(keysched + 0); + *(invkeysched + 9) = _mm_aesimc_si128(*(keysched + 1)); + *(invkeysched + 8) = _mm_aesimc_si128(*(keysched + 2)); + *(invkeysched + 7) = _mm_aesimc_si128(*(keysched + 3)); + *(invkeysched + 6) = _mm_aesimc_si128(*(keysched + 4)); + *(invkeysched + 5) = _mm_aesimc_si128(*(keysched + 5)); + *(invkeysched + 4) = _mm_aesimc_si128(*(keysched + 6)); + *(invkeysched + 3) = _mm_aesimc_si128(*(keysched + 7)); + *(invkeysched + 2) = _mm_aesimc_si128(*(keysched + 8)); + *(invkeysched + 1) = _mm_aesimc_si128(*(keysched + 9)); + *(invkeysched + 0) = *(keysched + 10); +} + +FUNC_ISA +static void aes_inv_key_12(AESContext * ctx) +{ + __m128i* keysched = (__m128i*)ctx->keysched; + __m128i* invkeysched = (__m128i*)ctx->invkeysched; + + *(invkeysched + 12) = *(keysched + 0); + *(invkeysched + 11) = _mm_aesimc_si128(*(keysched + 1)); + *(invkeysched + 10) = _mm_aesimc_si128(*(keysched + 2)); + *(invkeysched + 9) = _mm_aesimc_si128(*(keysched + 3)); + *(invkeysched + 8) = _mm_aesimc_si128(*(keysched + 4)); + *(invkeysched + 7) = _mm_aesimc_si128(*(keysched + 5)); + *(invkeysched + 6) = _mm_aesimc_si128(*(keysched + 6)); + *(invkeysched + 5) = _mm_aesimc_si128(*(keysched + 7)); + *(invkeysched + 4) = _mm_aesimc_si128(*(keysched + 8)); + *(invkeysched + 3) = _mm_aesimc_si128(*(keysched + 9)); + *(invkeysched + 2) = _mm_aesimc_si128(*(keysched + 10)); + *(invkeysched + 1) = _mm_aesimc_si128(*(keysched + 11)); + *(invkeysched + 0) = *(keysched + 12); +} + +FUNC_ISA +static void aes_inv_key_14(AESContext * ctx) +{ + __m128i* keysched = (__m128i*)ctx->keysched; + __m128i* invkeysched = (__m128i*)ctx->invkeysched; + + *(invkeysched + 14) = *(keysched + 0); + *(invkeysched + 13) = _mm_aesimc_si128(*(keysched + 1)); + *(invkeysched + 12) = _mm_aesimc_si128(*(keysched + 2)); + *(invkeysched + 11) = _mm_aesimc_si128(*(keysched + 3)); + *(invkeysched + 10) = _mm_aesimc_si128(*(keysched + 4)); + *(invkeysched + 9) = _mm_aesimc_si128(*(keysched + 5)); + *(invkeysched + 8) = _mm_aesimc_si128(*(keysched + 6)); + *(invkeysched + 7) = _mm_aesimc_si128(*(keysched + 7)); + *(invkeysched + 6) = _mm_aesimc_si128(*(keysched + 8)); + *(invkeysched + 5) = _mm_aesimc_si128(*(keysched + 9)); + *(invkeysched + 4) = _mm_aesimc_si128(*(keysched + 10)); + *(invkeysched + 3) = _mm_aesimc_si128(*(keysched + 11)); + *(invkeysched + 2) = _mm_aesimc_si128(*(keysched + 12)); + *(invkeysched + 1) = _mm_aesimc_si128(*(keysched + 13)); + *(invkeysched + 0) = *(keysched + 14); +} + +/* + * Set up an AESContext. `keylen' is measured in + * bytes; it can be either 16 (128-bit), 24 (192-bit), or 32 + * (256-bit). + */ +FUNC_ISA +static void aes_setup_ni(AESContext * ctx, unsigned char *key, int keylen) +{ + __m128i *keysched = (__m128i*)ctx->keysched; + + ctx->encrypt_cbc = aes_encrypt_cbc_ni; + ctx->decrypt_cbc = aes_decrypt_cbc_ni; + ctx->sdctr = aes_sdctr_ni; + + /* + * Now do the key setup itself. + */ + switch (keylen) { + case 16: + AES_128_Key_Expansion (key, keysched); + break; + case 24: + AES_192_Key_Expansion (key, keysched); + break; + case 32: + AES_256_Key_Expansion (key, keysched); + break; + default: + assert(0); + } + + /* + * Now prepare the modified keys for the inverse cipher. + */ + switch (ctx->Nr) { + case 10: + aes_inv_key_10(ctx); + break; + case 12: + aes_inv_key_12(ctx); + break; + case 14: + aes_inv_key_14(ctx); + break; + default: + assert(0); + } +} + +#else /* COMPILER_SUPPORTS_AES_NI */ + +static void aes_setup_ni(AESContext * ctx, unsigned char *key, int keylen) +{ + assert(0); +} + +INLINE static int supports_aes_ni() +{ + return 0; +} + +#endif /* COMPILER_SUPPORTS_AES_NI */ diff --git a/sshshare.c b/sshshare.c index 82c4bd31e..59cdad45b 100644 --- a/sshshare.c +++ b/sshshare.c @@ -911,8 +911,8 @@ static void share_disconnect(struct ssh_sharing_connstate *cs, share_begin_cleanup(cs); } -static int share_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +static void share_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) { struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug; @@ -935,7 +935,6 @@ static int share_closing(Plug plug, const char *error_msg, int error_code, "Socket error: %s", error_msg); } share_begin_cleanup(cs); - return 1; } static int getstring_inner(const void *vdata, int datalen, @@ -1775,17 +1774,17 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * Coroutine macros similar to, but simplified from, those in ssh.c. */ #define crBegin(v) { int *crLine = &v; switch(v) { case 0:; -#define crFinish(z) } *crLine = 0; return (z); } +#define crFinishV } *crLine = 0; return; } #define crGetChar(c) do \ { \ while (len == 0) { \ - *crLine =__LINE__; return 1; case __LINE__:; \ + *crLine =__LINE__; return; case __LINE__:; \ } \ len--; \ (c) = (unsigned char)*data++; \ } while (0) -static int share_receive(Plug plug, int urgent, char *data, int len) +static void share_receive(Plug plug, int urgent, char *data, int len) { struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug; static const char expected_verstring_prefix[] = @@ -1858,7 +1857,7 @@ static int share_receive(Plug plug, int urgent, char *data, int len) } dead:; - crFinish(1); + crFinishV; } static void share_sent(Plug plug, int bufsize) @@ -1875,8 +1874,8 @@ static void share_sent(Plug plug, int bufsize) */ } -static int share_listen_closing(Plug plug, const char *error_msg, - int error_code, int calling_back) +static void share_listen_closing(Plug plug, const char *error_msg, + int error_code, int calling_back) { struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)plug; if (error_msg) @@ -1884,7 +1883,6 @@ static int share_listen_closing(Plug plug, const char *error_msg, "listening socket: %s", error_msg); sk_close(sharestate->listensock); sharestate->listensock = NULL; - return 1; } static void share_send_verstring(struct ssh_sharing_connstate *cs) @@ -2047,10 +2045,9 @@ char *ssh_share_sockname(const char *host, int port, Conf *conf) static void nullplug_socket_log(Plug plug, int type, SockAddr addr, int port, const char *error_msg, int error_code) {} -static int nullplug_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) { return 0; } -static int nullplug_receive(Plug plug, int urgent, char *data, - int len) { return 0; } +static void nullplug_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) {} +static void nullplug_receive(Plug plug, int urgent, char *data, int len) {} static void nullplug_sent(Plug plug, int bufsize) {} int ssh_share_test_for_upstream(const char *host, int port, Conf *conf) diff --git a/telnet.c b/telnet.c index c4b041327..8d03cb7b9 100644 --- a/telnet.c +++ b/telnet.c @@ -658,8 +658,8 @@ static void telnet_log(Plug plug, int type, SockAddr addr, int port, telnet->session_started); } -static int telnet_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +static void telnet_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) { Telnet telnet = (Telnet) plug; @@ -681,17 +681,15 @@ static int telnet_closing(Plug plug, const char *error_msg, int error_code, connection_fatal(telnet->frontend, "%s", error_msg); } /* Otherwise, the remote side closed the connection normally. */ - return 0; } -static int telnet_receive(Plug plug, int urgent, char *data, int len) +static void telnet_receive(Plug plug, int urgent, char *data, int len) { Telnet telnet = (Telnet) plug; if (urgent) telnet->in_synch = TRUE; telnet->session_started = TRUE; do_telnet_read(telnet, data, len); - return 1; } static void telnet_sent(Plug plug, int bufsize) diff --git a/terminal.c b/terminal.c index c79944cda..06f41e446 100644 --- a/terminal.c +++ b/terminal.c @@ -108,6 +108,7 @@ static void update_sbar(Terminal *); static void deselect(Terminal *); static void term_print_finish(Terminal *); static void scroll(Terminal *, int, int, int, int); +static void parse_optionalrgb(optionalrgb *out, unsigned *values); #ifdef OPTIMISE_SCROLL static void scroll_display(Terminal *, int, int, int); #endif /* OPTIMISE_SCROLL */ @@ -283,6 +284,8 @@ static int termchars_equal_override(termchar *a, termchar *b, unsigned long bchr, unsigned long battr) { /* FULL-TERMCHAR */ + if (!truecolour_equal(a->truecolour, b->truecolour)) + return FALSE; if (a->chr != bchr) return FALSE; if ((a->attr &~ DATTR_MASK) != (battr &~ DATTR_MASK)) @@ -607,6 +610,24 @@ static void makeliteral_attr(struct buf *b, termchar *c, unsigned long *state) add(b, (unsigned char)(attr & 0xFF)); } } +static void makeliteral_truecolour(struct buf *b, termchar *c, unsigned long *state) +{ + /* + * Put the used parts of the colour info into the buffer. + */ + add(b, ((c->truecolour.fg.enabled ? 1 : 0) | + (c->truecolour.bg.enabled ? 2 : 0))); + if (c->truecolour.fg.enabled) { + add(b, c->truecolour.fg.r); + add(b, c->truecolour.fg.g); + add(b, c->truecolour.fg.b); + } + if (c->truecolour.bg.enabled) { + add(b, c->truecolour.bg.r); + add(b, c->truecolour.bg.g); + add(b, c->truecolour.bg.b); + } +} static void makeliteral_cc(struct buf *b, termchar *c, unsigned long *state) { /* @@ -681,6 +702,7 @@ static unsigned char *compressline(termline *ldata) */ makerle(b, ldata, makeliteral_chr); makerle(b, ldata, makeliteral_attr); + makerle(b, ldata, makeliteral_truecolour); makerle(b, ldata, makeliteral_cc); /* @@ -826,6 +848,29 @@ static void readliteral_attr(struct buf *b, termchar *c, termline *ldata, c->attr = attr; } +static void readliteral_truecolour(struct buf *b, termchar *c, termline *ldata, + unsigned long *state) +{ + int flags = get(b); + + if (flags & 1) { + c->truecolour.fg.enabled = TRUE; + c->truecolour.fg.r = get(b); + c->truecolour.fg.g = get(b); + c->truecolour.fg.b = get(b); + } else { + c->truecolour.fg = optionalrgb_none; + } + + if (flags & 2) { + c->truecolour.bg.enabled = TRUE; + c->truecolour.bg.r = get(b); + c->truecolour.bg.g = get(b); + c->truecolour.bg.b = get(b); + } else { + c->truecolour.bg = optionalrgb_none; + } +} static void readliteral_cc(struct buf *b, termchar *c, termline *ldata, unsigned long *state) { @@ -899,6 +944,7 @@ static termline *decompressline(unsigned char *data, int *bytes_used) */ readrle(b, ldata, readliteral_chr); readrle(b, ldata, readliteral_attr); + readrle(b, ldata, readliteral_truecolour); readrle(b, ldata, readliteral_cc); /* Return the number of bytes read, for diagnostic purposes. */ @@ -1039,19 +1085,19 @@ static termline *lineptr(Terminal *term, int y, int lineno, int screen) /* We assume that we don't screw up and retrieve something out of range. */ if (line == NULL) { - fatalbox("line==NULL in terminal.c\n" - "lineno=%d y=%d w=%d h=%d\n" - "count(scrollback=%p)=%d\n" - "count(screen=%p)=%d\n" - "count(alt=%p)=%d alt_sblines=%d\n" - "whichtree=%p treeindex=%d\n\n" - "Please contact " - "and pass on the above information.", - lineno, y, term->cols, term->rows, - term->scrollback, count234(term->scrollback), - term->screen, count234(term->screen), - term->alt_screen, count234(term->alt_screen), term->alt_sblines, - whichtree, treeindex); + modalfatalbox("line==NULL in terminal.c\n" + "lineno=%d y=%d w=%d h=%d\n" + "count(scrollback=%p)=%d\n" + "count(screen=%p)=%d\n" + "count(alt=%p)=%d alt_sblines=%d\n" + "whichtree=%p treeindex=%d\n\n" + "Please contact " + "and pass on the above information.", + lineno, y, term->cols, term->rows, + term->scrollback, count234(term->scrollback), + term->screen, count234(term->screen), + term->alt_screen, count234(term->alt_screen), + term->alt_sblines, whichtree, treeindex); } assert(line != NULL); @@ -1250,6 +1296,8 @@ static void power_on(Terminal *term, int clear) term->big_cursor = 0; term->default_attr = term->save_attr = term->alt_save_attr = term->curr_attr = ATTR_DEFAULT; + term->curr_truecolour.fg = term->curr_truecolour.bg = optionalrgb_none; + term->save_truecolour = term->alt_save_truecolour = term->curr_truecolour; term->term_editing = term->term_echoing = FALSE; term->app_cursor_keys = conf_get_int(term->conf, CONF_app_cursor); term->app_keypad_keys = conf_get_int(term->conf, CONF_app_keypad); @@ -1360,9 +1408,11 @@ void term_pwron(Terminal *term, int clear) static void set_erase_char(Terminal *term) { term->erase_char = term->basic_erase_char; - if (term->use_bce) + if (term->use_bce) { term->erase_char.attr = (term->curr_attr & (ATTR_FGMASK | ATTR_BGMASK)); + term->erase_char.truecolour.bg = term->curr_truecolour.bg; + } } /* @@ -1411,6 +1461,7 @@ void term_copy_stuff_from_conf(Terminal *term) term->scroll_on_disp = conf_get_int(term->conf, CONF_scroll_on_disp); term->scroll_on_key = conf_get_int(term->conf, CONF_scroll_on_key); term->xterm_256_colour = conf_get_int(term->conf, CONF_xterm_256_colour); + term->true_colour = conf_get_int(term->conf, CONF_true_colour); /* * Parse the control-character escapes in the configured @@ -1570,6 +1621,8 @@ void term_clrsb(Terminal *term) update_sbar(term); } +const optionalrgb optionalrgb_none = {0, 0, 0, 0}; + /* * Initialise the terminal. */ @@ -1646,8 +1699,20 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, term->basic_erase_char.chr = CSET_ASCII | ' '; term->basic_erase_char.attr = ATTR_DEFAULT; term->basic_erase_char.cc_next = 0; + term->basic_erase_char.truecolour.fg = optionalrgb_none; + term->basic_erase_char.truecolour.bg = optionalrgb_none; term->erase_char = term->basic_erase_char; + term->last_selected_text = NULL; + term->last_selected_attr = NULL; + term->last_selected_tc = NULL; + term->last_selected_len = 0; + /* frontends will typically extend these with clipboard ids they + * know about */ + term->mouse_select_clipboards[0] = CLIP_LOCAL; + term->n_mouse_select_clipboards = 1; + term->mouse_paste_clipboard = CLIP_NULL; + return term; } @@ -1684,6 +1749,7 @@ void term_free(Terminal *term) sfree(term->ltemp); sfree(term->wcFrom); sfree(term->wcTo); + sfree(term->answerback); for (i = 0; i < term->bidi_cache_size; i++) { sfree(term->pre_bidi_cache[i].chars); @@ -1933,6 +1999,7 @@ static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos) { int t; pos tp; + truecolour ttc; tree234 *ttr; if (!which) @@ -1997,6 +2064,10 @@ static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos) if (!reset && !keep_cur_pos) term->save_attr = term->alt_save_attr; term->alt_save_attr = t; + ttc = term->save_truecolour; + if (!reset && !keep_cur_pos) + term->save_truecolour = term->alt_save_truecolour; + term->alt_save_truecolour = ttc; t = term->save_utf; if (!reset && !keep_cur_pos) term->save_utf = term->alt_save_utf; @@ -2292,6 +2363,7 @@ static void save_cursor(Terminal *term, int save) if (save) { term->savecurs = term->curs; term->save_attr = term->curr_attr; + term->save_truecolour = term->curr_truecolour; term->save_cset = term->cset; term->save_utf = term->utf; term->save_wnext = term->wrapnext; @@ -2306,6 +2378,7 @@ static void save_cursor(Terminal *term, int save) term->curs.y = term->rows - 1; term->curr_attr = term->save_attr; + term->curr_truecolour = term->save_truecolour; term->cset = term->save_cset; term->utf = term->save_utf; term->wrapnext = term->save_wnext; @@ -2437,7 +2510,7 @@ static void erase_lots(Terminal *term, /* After an erase of lines from the top of the screen, we shouldn't * bring the lines back again if the terminal enlarges (since the user or - * application has explictly thrown them away). */ + * application has explicitly thrown them away). */ if (erasing_lines_from_top && !(term->alt_which)) term->tempsblines = 0; } @@ -2675,6 +2748,22 @@ static void do_osc(Terminal *term) if (!term->no_remote_wintitle) set_title(term->frontend, term->osc_string); break; + case 4: + if (term->ldisc && !strcmp(term->osc_string, "?")) { + int r, g, b; + if (palette_get(term->frontend, toint(term->esc_args[1]), + &r, &g, &b)) { + char *reply_buf = dupprintf( + "\033]4;%u;rgb:%04x/%04x/%04x\007", + term->esc_args[1], + (unsigned)r * 0x0101, + (unsigned)g * 0x0101, + (unsigned)b * 0x0101); + ldisc_send(term->ldisc, reply_buf, strlen(reply_buf), 0); + sfree(reply_buf); + } + } + break; } } } @@ -3212,6 +3301,8 @@ static void term_out(Terminal *term) clear_cc(cline, term->curs.x); cline->chars[term->curs.x].chr = c; cline->chars[term->curs.x].attr = term->curr_attr; + cline->chars[term->curs.x].truecolour = + term->curr_truecolour; term->curs.x++; @@ -3219,6 +3310,8 @@ static void term_out(Terminal *term) clear_cc(cline, term->curs.x); cline->chars[term->curs.x].chr = UCSWIDE; cline->chars[term->curs.x].attr = term->curr_attr; + cline->chars[term->curs.x].truecolour = + term->curr_truecolour; break; case 1: @@ -3229,6 +3322,8 @@ static void term_out(Terminal *term) clear_cc(cline, term->curs.x); cline->chars[term->curs.x].chr = c; cline->chars[term->curs.x].attr = term->curr_attr; + cline->chars[term->curs.x].truecolour = + term->curr_truecolour; break; case 0: @@ -3307,6 +3402,7 @@ static void term_out(Terminal *term) compatibility(OTHER); term->termstate = SEEN_OSC; term->esc_args[0] = 0; + term->esc_nargs = 1; break; case '7': /* DECSC: save cursor */ compatibility(VT100); @@ -3355,7 +3451,7 @@ static void term_out(Terminal *term) break; case 'Z': /* DECID: terminal type query */ compatibility(VT100); - if (term->ldisc) + if (term->ldisc && term->id_string[0]) ldisc_send(term->ldisc, term->id_string, strlen(term->id_string), 0); break; @@ -3662,7 +3758,7 @@ static void term_out(Terminal *term) case 'c': /* DA: terminal type query */ compatibility(VT100); /* This is the response for a VT102 */ - if (term->ldisc) + if (term->ldisc && term->id_string[0]) ldisc_send(term->ldisc, term->id_string, strlen(term->id_string), 0); break; @@ -3799,11 +3895,17 @@ static void term_out(Terminal *term) switch (def(term->esc_args[i], 0)) { case 0: /* restore defaults */ term->curr_attr = term->default_attr; + term->curr_truecolour = + term->basic_erase_char.truecolour; break; case 1: /* enable bold */ compatibility(VT100AVO); term->curr_attr |= ATTR_BOLD; break; + case 2: /* enable dim */ + compatibility(OTHER); + term->curr_attr |= ATTR_DIM; + break; case 21: /* (enable double underline) */ compatibility(OTHER); case 4: /* enable underline */ @@ -3835,9 +3937,9 @@ static void term_out(Terminal *term) compatibility(SCOANSI); if (term->no_remote_charset) break; term->sco_acs = 2; break; - case 22: /* disable bold */ + case 22: /* disable bold and dim */ compatibility2(OTHER, VT220); - term->curr_attr &= ~ATTR_BOLD; + term->curr_attr &= ~(ATTR_BOLD | ATTR_DIM); break; case 24: /* disable underline */ compatibility2(OTHER, VT220); @@ -3860,6 +3962,7 @@ static void term_out(Terminal *term) case 36: case 37: /* foreground */ + term->curr_truecolour.fg.enabled = FALSE; term->curr_attr &= ~ATTR_FGMASK; term->curr_attr |= (term->esc_args[i] - 30)<curr_truecolour.fg.enabled = FALSE; term->curr_attr &= ~ATTR_FGMASK; term->curr_attr |= ((term->esc_args[i] - 90 + 8) << ATTR_FGSHIFT); break; case 39: /* default-foreground */ + term->curr_truecolour.fg.enabled = FALSE; term->curr_attr &= ~ATTR_FGMASK; term->curr_attr |= ATTR_DEFFG; break; @@ -3891,6 +3996,7 @@ static void term_out(Terminal *term) case 46: case 47: /* background */ + term->curr_truecolour.bg.enabled = FALSE; term->curr_attr &= ~ATTR_BGMASK; term->curr_attr |= (term->esc_args[i] - 40)<curr_truecolour.bg.enabled = FALSE; term->curr_attr &= ~ATTR_BGMASK; term->curr_attr |= ((term->esc_args[i] - 100 + 8) << ATTR_BGSHIFT); break; case 49: /* default-background */ + term->curr_truecolour.bg.enabled = FALSE; term->curr_attr &= ~ATTR_BGMASK; term->curr_attr |= ATTR_DEFBG; break; - case 38: /* xterm 256-colour mode */ + + /* + * 256-colour and true-colour + * sequences. A 256-colour + * foreground is selected by a + * sequence of 3 arguments in the + * form 38;5;n, where n is in the + * range 0-255. A true-colour RGB + * triple is selected by 5 args of + * the form 38;2;r;g;b. Replacing + * the initial 38 with 48 in both + * cases selects the same colour + * as the background. + */ + case 38: if (i+2 < term->esc_nargs && term->esc_args[i+1] == 5) { term->curr_attr &= ~ATTR_FGMASK; term->curr_attr |= ((term->esc_args[i+2] & 0xFF) << ATTR_FGSHIFT); + term->curr_truecolour.fg = + optionalrgb_none; i += 2; + } + if (i + 4 < term->esc_nargs && + term->esc_args[i + 1] == 2) { + parse_optionalrgb( + &term->curr_truecolour.fg, + term->esc_args + (i+2)); + i += 4; } break; - case 48: /* xterm 256-colour mode */ + case 48: if (i+2 < term->esc_nargs && term->esc_args[i+1] == 5) { term->curr_attr &= ~ATTR_BGMASK; term->curr_attr |= ((term->esc_args[i+2] & 0xFF) << ATTR_BGSHIFT); + term->curr_truecolour.bg = + optionalrgb_none; i += 2; } + if (i + 4 < term->esc_nargs && + term->esc_args[i+1] == 2) { + parse_optionalrgb( + &term->curr_truecolour.bg, + term->esc_args + (i+2)); + i += 4; + } break; } } @@ -4069,7 +4209,8 @@ static void term_out(Terminal *term) p = EMPTY_WINDOW_TITLE; len = strlen(p); ldisc_send(term->ldisc, "\033]L", 3, 0); - ldisc_send(term->ldisc, p, len, 0); + if (len > 0) + ldisc_send(term->ldisc, p, len, 0); ldisc_send(term->ldisc, "\033\\", 2, 0); } break; @@ -4082,7 +4223,8 @@ static void term_out(Terminal *term) p = EMPTY_WINDOW_TITLE; len = strlen(p); ldisc_send(term->ldisc, "\033]l", 3, 0); - ldisc_send(term->ldisc, p, len, 0); + if (len > 0) + ldisc_send(term->ldisc, p, len, 0); ldisc_send(term->ldisc, "\033\\", 2, 0); } break; @@ -4243,6 +4385,7 @@ static void term_out(Terminal *term) ATTR_FGSHIFT; term->curr_attr &= ~ATTR_FGMASK; term->curr_attr |= colour; + term->curr_truecolour.fg = optionalrgb_none; term->default_attr &= ~ATTR_FGMASK; term->default_attr |= colour; set_erase_char(term); @@ -4257,6 +4400,7 @@ static void term_out(Terminal *term) ATTR_BGSHIFT; term->curr_attr &= ~ATTR_BGMASK; term->curr_attr |= colour; + term->curr_truecolour.bg = optionalrgb_none; term->default_attr &= ~ATTR_BGMASK; term->default_attr |= colour; set_erase_char(term); @@ -4327,7 +4471,7 @@ static void term_out(Terminal *term) for (i = 1; i < term->esc_nargs; i++) { if (i != 1) strcat(term->id_string, ";"); - sprintf(lbuf, "%d", term->esc_args[i]); + sprintf(lbuf, "%u", term->esc_args[i]); strcat(term->id_string, lbuf); } strcat(term->id_string, "c"); @@ -4374,25 +4518,40 @@ static void term_out(Terminal *term) case '7': case '8': case '9': - if (term->esc_args[0] <= UINT_MAX / 10 && - term->esc_args[0] * 10 <= UINT_MAX - c - '0') - term->esc_args[0] = 10 * term->esc_args[0] + c - '0'; + if (term->esc_args[term->esc_nargs-1] <= UINT_MAX / 10 && + term->esc_args[term->esc_nargs-1] * 10 <= UINT_MAX - c - '0') + term->esc_args[term->esc_nargs-1] = + 10 * term->esc_args[term->esc_nargs-1] + c - '0'; else - term->esc_args[0] = UINT_MAX; + term->esc_args[term->esc_nargs-1] = UINT_MAX; break; - case 'L': - /* - * Grotty hack to support xterm and DECterm title - * sequences concurrently. - */ - if (term->esc_args[0] == 2) { - term->esc_args[0] = 1; - break; - } - /* else fall through */ - default: - term->termstate = OSC_STRING; - term->osc_strlen = 0; + default: + /* + * _Most_ other characters here terminate the + * immediate parsing of the OSC sequence and go + * into OSC_STRING state, but we deal with a + * couple of exceptions first. + */ + if (c == 'L' && term->esc_args[0] == 2) { + /* + * Grotty hack to support xterm and DECterm title + * sequences concurrently. + */ + term->esc_args[0] = 1; + } else if (c == ';' && term->esc_nargs == 1 && + term->esc_args[0] == 4) { + /* + * xterm's OSC 4 sequence to query the current + * RGB value of a colour takes a second + * numeric argument which is easiest to parse + * using the existing system rather than in + * do_osc. + */ + term->esc_args[term->esc_nargs++] = 0; + } else { + term->termstate = OSC_STRING; + term->osc_strlen = 0; + } } break; case OSC_STRING: @@ -4669,6 +4828,8 @@ static void term_out(Terminal *term) /* compatibility(OTHER) */ term->vt52_bold = FALSE; term->curr_attr = ATTR_DEFAULT; + term->curr_truecolour.fg = optionalrgb_none; + term->curr_truecolour.bg = optionalrgb_none; set_erase_char(term); break; case 'S': @@ -4731,6 +4892,19 @@ static void term_out(Terminal *term) logflush(term->logctx); } +/* + * Small subroutine to parse three consecutive escape-sequence + * arguments representing a true-colour RGB triple into an + * optionalrgb. + */ +static void parse_optionalrgb(optionalrgb *out, unsigned *values) +{ + out->enabled = TRUE; + out->r = values[0] < 256 ? values[0] : 0; + out->g = values[1] < 256 ? values[1] : 0; + out->b = values[2] < 256 ? values[2] : 0; +} + /* * To prevent having to run the reasonably tricky bidi algorithm * too many times, we maintain a cache of the last lineful of data @@ -5033,6 +5207,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) int last_run_dirty = 0; int laststart, dirtyrect; int *backward; + truecolour tc; scrpos.y = i + term->disptop; ldata = lineptr(scrpos.y); @@ -5072,6 +5247,12 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) tattr = (tattr &~ ATTR_BGMASK) | ATTR_DEFBG; } + if (term->true_colour) { + tc = d->truecolour; + } else { + tc.fg = tc.bg = optionalrgb_none; + } + switch (tchar & CSET_MASK) { case CSET_ASCII: tchar = term->ucsdata->unitab_line[tchar & 0xFF]; @@ -5129,6 +5310,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) /* FULL-TERMCHAR */ newline[j].attr = tattr; newline[j].chr = tchar; + newline[j].truecolour = tc; /* Combining characters are still read from lchars */ newline[j].cc_next = 0; } @@ -5179,6 +5361,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) term->disptext[i]->lattr); term->disptext[i]->lattr = ldata->lattr; + tc = term->erase_char.truecolour; for (j = 0; j < term->cols; j++) { unsigned long tattr, tchar; int break_run, do_copy; @@ -5192,6 +5375,9 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) break_run = ((tattr ^ attr) & term->attr_mask) != 0; + if (!truecolour_equal(newline[j].truecolour, tc)) + break_run = TRUE; + #ifdef USES_VTLINE_HACK /* Special hack for VT100 Linedraw glyphs */ if ((tchar >= 0x23BA && tchar <= 0x23BD) || @@ -5224,15 +5410,15 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) if (break_run) { if ((dirty_run || last_run_dirty) && ccount > 0) { - do_text(ctx, start, i, ch, ccount, attr, - ldata->lattr); + do_text(ctx, start, i, ch, ccount, attr, ldata->lattr, tc); if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) do_cursor(ctx, start, i, ch, ccount, attr, - ldata->lattr); + ldata->lattr, tc); } start = j; ccount = 0; attr = tattr; + tc = newline[j].truecolour; cset = CSET_OF(tchar); if (term->ucsdata->dbcs_screenfont) last_run_dirty = dirty_run; @@ -5301,6 +5487,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) copy_termchar(term->disptext[i], j, d); term->disptext[i]->chars[j].chr = tchar; term->disptext[i]->chars[j].attr = tattr; + term->disptext[i]->chars[j].truecolour = tc; if (start == j) term->disptext[i]->chars[j].attr |= DATTR_STARTRUN; } @@ -5322,11 +5509,9 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) } } if (dirty_run && ccount > 0) { - do_text(ctx, start, i, ch, ccount, attr, - ldata->lattr); + do_text(ctx, start, i, ch, ccount, attr, ldata->lattr, tc); if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) - do_cursor(ctx, start, i, ch, ccount, attr, - ldata->lattr); + do_cursor(ctx, start, i, ch, ccount, attr, ldata->lattr, tc); } unlineptr(ldata); @@ -5442,9 +5627,11 @@ typedef struct { wchar_t *textptr; /* = textbuf + bufpos (current insertion point) */ int *attrbuf; /* buffer for copied attributes */ int *attrptr; /* = attrbuf + bufpos */ + truecolour *tcbuf; /* buffer for copied colours */ + truecolour *tcptr; /* = tcbuf + bufpos */ } clip_workbuf; -static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr) +static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr, truecolour tc) { if (b->bufpos >= b->buflen) { b->buflen *= 2; @@ -5452,22 +5639,28 @@ static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr) b->textptr = b->textbuf + b->bufpos; b->attrbuf = sresize(b->attrbuf, b->buflen, int); b->attrptr = b->attrbuf + b->bufpos; + b->tcbuf = sresize(b->tcbuf, b->buflen, truecolour); + b->tcptr = b->tcbuf + b->bufpos; } *b->textptr++ = chr; *b->attrptr++ = attr; + *b->tcptr++ = tc; b->bufpos++; } -static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) +static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel, + const int *clipboards, int n_clipboards) { clip_workbuf buf; int old_top_x; int attr; + truecolour tc; buf.buflen = 5120; buf.bufpos = 0; buf.textptr = buf.textbuf = snewn(buf.buflen, wchar_t); buf.attrptr = buf.attrbuf = snewn(buf.buflen, int); + buf.tcptr = buf.tcbuf = snewn(buf.buflen, truecolour); old_top_x = top.x; /* needed for rect==1 */ @@ -5533,6 +5726,7 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) while (1) { int uc = ldata->chars[x].chr; attr = ldata->chars[x].attr; + tc = ldata->chars[x].truecolour; switch (uc & CSET_MASK) { case CSET_LINEDRW: @@ -5593,7 +5787,7 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) #endif for (p = cbuf; *p; p++) - clip_addchar(&buf, *p, attr); + clip_addchar(&buf, *p, attr, tc); if (ldata->chars[x].cc_next) x += ldata->chars[x].cc_next; @@ -5605,7 +5799,7 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) if (nl) { int i; for (i = 0; i < sel_nl_sz; i++) - clip_addchar(&buf, sel_nl[i], 0); + clip_addchar(&buf, sel_nl[i], 0, term->basic_erase_char.truecolour); } top.y++; top.x = rect ? old_top_x : 0; @@ -5613,15 +5807,38 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) unlineptr(ldata); } #if SELECTION_NUL_TERMINATED - clip_addchar(&buf, 0, 0); + clip_addchar(&buf, 0, 0, term->basic_erase_char.truecolour); #endif - /* Finally, transfer all that to the clipboard. */ - write_clip(term->frontend, buf.textbuf, buf.attrbuf, buf.bufpos, desel); - sfree(buf.textbuf); - sfree(buf.attrbuf); + /* Finally, transfer all that to the clipboard(s). */ + { + int i; + int clip_local = FALSE; + for (i = 0; i < n_clipboards; i++) { + if (clipboards[i] == CLIP_LOCAL) { + clip_local = TRUE; + } else if (clipboards[i] != CLIP_NULL) { + write_clip(term->frontend, clipboards[i], + buf.textbuf, buf.attrbuf, buf.tcbuf, buf.bufpos, + desel); + } + } + if (clip_local) { + sfree(term->last_selected_text); + sfree(term->last_selected_attr); + sfree(term->last_selected_tc); + term->last_selected_text = buf.textbuf; + term->last_selected_attr = buf.attrbuf; + term->last_selected_tc = buf.tcbuf; + term->last_selected_len = buf.bufpos; + } else { + sfree(buf.textbuf); + sfree(buf.attrbuf); + sfree(buf.tcbuf); + } + } } -void term_copyall(Terminal *term) +void term_copyall(Terminal *term, const int *clipboards, int n_clipboards) { pos top; pos bottom; @@ -5630,7 +5847,44 @@ void term_copyall(Terminal *term) top.x = 0; bottom.y = find_last_nonempty_line(term, screen); bottom.x = term->cols; - clipme(term, top, bottom, 0, TRUE); + clipme(term, top, bottom, 0, TRUE, clipboards, n_clipboards); +} + +static void paste_from_clip_local(void *vterm) +{ + Terminal *term = (Terminal *)vterm; + term_do_paste(term, term->last_selected_text, term->last_selected_len); +} + +void term_request_copy(Terminal *term, const int *clipboards, int n_clipboards) +{ + int i; + for (i = 0; i < n_clipboards; i++) { + assert(clipboards[i] != CLIP_LOCAL); + if (clipboards[i] != CLIP_NULL) { + write_clip(term->frontend, clipboards[i], + term->last_selected_text, + term->last_selected_attr, + term->last_selected_tc, + term->last_selected_len, + FALSE); + } + } +} + +void term_request_paste(Terminal *term, int clipboard) +{ + switch (clipboard) { + case CLIP_NULL: + /* Do nothing: CLIP_NULL never has data in it. */ + break; + case CLIP_LOCAL: + queue_toplevel_callback(paste_from_clip_local, term); + break; + default: + frontend_request_paste(term->frontend, clipboard); + break; + } } /* @@ -5889,66 +6143,66 @@ static void term_paste_callback(void *vterm) term->paste_len = 0; } -void term_do_paste(Terminal *term) +void term_do_paste(Terminal *term, const wchar_t *data, int len) { - wchar_t *data; - int len; - - get_clip(term->frontend, &data, &len); - if (data && len > 0) { - wchar_t *p, *q; + const wchar_t *p, *q; - term_seen_key_event(term); /* pasted data counts */ - - if (term->paste_buffer) - sfree(term->paste_buffer); - term->paste_pos = term->paste_len = 0; - term->paste_buffer = snewn(len + 12, wchar_t); - - if (term->bracketed_paste) { - memcpy(term->paste_buffer, L"\033[200~", 6 * sizeof(wchar_t)); - term->paste_len += 6; - } - - p = q = data; - while (p < data + len) { - while (p < data + len && - !(p <= data + len - sel_nl_sz && - !memcmp(p, sel_nl, sizeof(sel_nl)))) - p++; - - { - int i; - for (i = 0; i < p - q; i++) { - term->paste_buffer[term->paste_len++] = q[i]; - } - } + /* + * Pasting data into the terminal counts as a keyboard event (for + * purposes of the 'Reset scrollback on keypress' config option), + * unless the paste is zero-length. + */ + if (len == 0) + return; + term_seen_key_event(term); + + if (term->paste_buffer) + sfree(term->paste_buffer); + term->paste_pos = term->paste_len = 0; + term->paste_buffer = snewn(len + 12, wchar_t); + + if (term->bracketed_paste) { + memcpy(term->paste_buffer, L"\033[200~", 6 * sizeof(wchar_t)); + term->paste_len += 6; + } - if (p <= data + len - sel_nl_sz && - !memcmp(p, sel_nl, sizeof(sel_nl))) { - term->paste_buffer[term->paste_len++] = '\015'; - p += sel_nl_sz; + p = q = data; + while (p < data + len) { + while (p < data + len && + !(p <= data + len - sel_nl_sz && + !memcmp(p, sel_nl, sizeof(sel_nl)))) + p++; + + { + int i; + for (i = 0; i < p - q; i++) { + term->paste_buffer[term->paste_len++] = q[i]; } - q = p; } - if (term->bracketed_paste) { - memcpy(term->paste_buffer + term->paste_len, - L"\033[201~", 6 * sizeof(wchar_t)); - term->paste_len += 6; + if (p <= data + len - sel_nl_sz && + !memcmp(p, sel_nl, sizeof(sel_nl))) { + term->paste_buffer[term->paste_len++] = '\015'; + p += sel_nl_sz; } + q = p; + } - /* Assume a small paste will be OK in one go. */ - if (term->paste_len < 256) { - if (term->ldisc) - luni_send(term->ldisc, term->paste_buffer, term->paste_len, 0); - if (term->paste_buffer) - sfree(term->paste_buffer); - term->paste_buffer = 0; - term->paste_pos = term->paste_len = 0; - } + if (term->bracketed_paste) { + memcpy(term->paste_buffer + term->paste_len, + L"\033[201~", 6 * sizeof(wchar_t)); + term->paste_len += 6; + } + + /* Assume a small paste will be OK in one go. */ + if (term->paste_len < 256) { + if (term->ldisc) + luni_send(term->ldisc, term->paste_buffer, term->paste_len, 0); + if (term->paste_buffer) + sfree(term->paste_buffer); + term->paste_buffer = 0; + term->paste_pos = term->paste_len = 0; } - get_clip(term->frontend, NULL, NULL); queue_toplevel_callback(term_paste_callback, term); } @@ -5974,7 +6228,18 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, term_scroll(term, 0, +1); } if (x < 0) { - if (y > 0) { + if (y > 0 && !raw_mouse && term->seltype != RECTANGULAR) { + /* + * When we're using the mouse for normal raster-based + * selection, dragging off the left edge of a terminal row + * is treated the same as the right-hand end of the + * previous row, in that it's considered to identify a + * point _before_ the first character on row y. + * + * But if the mouse action is going to be used for + * anything else - rectangular selection, or xterm mouse + * tracking - then we disable this special treatment. + */ x = term->cols - 1; y--; } else @@ -6202,7 +6467,9 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, * data to the clipboard. */ clipme(term, term->selstart, term->selend, - (term->seltype == RECTANGULAR), FALSE); + (term->seltype == RECTANGULAR), FALSE, + term->mouse_select_clipboards, + term->n_mouse_select_clipboards); term->selstate = SELECTED; } else term->selstate = NO_SELECTION; @@ -6212,7 +6479,7 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, || a == MA_2CLK || a == MA_3CLK #endif )) { - request_paste(term->frontend); + term_request_paste(term, term->mouse_paste_clipboard); } /* @@ -6276,8 +6543,12 @@ static void deselect(Terminal *term) term->selstart.x = term->selstart.y = term->selend.x = term->selend.y = 0; } -void term_deselect(Terminal *term) +void term_lost_clipboard_ownership(Terminal *term, int clipboard) { + if (!(term->n_mouse_select_clipboards > 1 && + clipboard == term->mouse_select_clipboards[1])) + return; + deselect(term); term_update(term); diff --git a/terminal.h b/terminal.h index 2ed9e6ef5..0057e34ff 100644 --- a/terminal.h +++ b/terminal.h @@ -40,6 +40,7 @@ struct termchar { */ unsigned long chr; unsigned long attr; + truecolour truecolour; /* * The cc_next field is used to link multiple termchars @@ -102,6 +103,7 @@ struct terminal_tag { #endif /* OPTIMISE_SCROLL */ int default_attr, curr_attr, save_attr; + truecolour curr_truecolour, save_truecolour; termchar basic_erase_char, erase_char; bufchain inbuf; /* terminal input buffer */ @@ -138,6 +140,7 @@ struct terminal_tag { /* ESC 7 saved state for the alternate screen */ pos alt_savecurs; int alt_save_attr; + truecolour alt_save_truecolour; int alt_save_cset, alt_save_csattr; int alt_save_utf, alt_save_wnext; int alt_save_sco_acs; @@ -323,6 +326,15 @@ struct terminal_tag { int scroll_on_disp; int scroll_on_key; int xterm_256_colour; + int true_colour; + + wchar_t *last_selected_text; + int *last_selected_attr; + truecolour *last_selected_tc; + size_t last_selected_len; + int mouse_select_clipboards[N_CLIPBOARDS]; + int n_mouse_select_clipboards; + int mouse_paste_clipboard; }; #define in_utf(term) ((term)->utf || (term)->ucsdata->line_codepage==CP_UTF8) diff --git a/testdata/colours.txt b/testdata/colours.txt index 33709d238..34dff8a5a 100644 --- a/testdata/colours.txt +++ b/testdata/colours.txt @@ -4,19 +4,12 @@ Normal text and bold; reverse video and bold ANSI plus bold: 0 bold 1 bold 2 bold 3 bold 4 bold 5 bold 6 bold 7 bold xterm bright: fg0 bg0 fg1 bg1 fg2 bg2 fg3 bg3 fg4 bg4 fg5 bg5 fg6 bg6 fg7 bg7 xterm 256: greys                      reds   greens blues  yellow magent cyans  - 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 - 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 - 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 - 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 - 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 - 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 - 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 - 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 - 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 - 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 - 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 - 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 - 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 - 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 - 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 +0001020304050607 08090a0b0c0d0e0f 1011121314151617 18191a1b1c1d1e1f +2021222324252627 28292a2b2c2d2e2f 3031323334353637 38393a3b3c3d3e3f +4041424344454647 48494a4b4c4d4e4f 5051525354555657 58595a5b5c5d5e5f +6061626364656667 68696a6b6c6d6e6f 7071727374757677 78797a7b7c7d7e7f +8081828384858687 88898a8b8c8d8e8f 9091929394959697 98999a9b9c9d9e9f +a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf +c0c1c2c3c4c5c6c7 c8c9cacbcccdcecf d0d1d2d3d4d5d6d7 d8d9dadbdcdddedf +e0e1e2e3e4e5e6e7 e8e9eaebecedeeef f0f1f2f3f4f5f6f7 f8f9fafbfcfdfeff +24-bit colour: SlateGrey OliveDrab goldenrod SaddleBrown DarkViolet (bg) diff --git a/tree234.c b/tree234.c index f1c0c2edb..d3c5293d8 100644 --- a/tree234.c +++ b/tree234.c @@ -681,7 +681,7 @@ static void *delpos234_internal(tree234 * t, int index) LOG((" moving to subtree %d\n", ki)); sub = n->kids[ki]; if (!sub->elems[1]) { - LOG((" subtree has only one element!\n", ki)); + LOG((" subtree has only one element!\n")); if (ki > 0 && n->kids[ki - 1]->elems[1]) { /* * Case 3a, left-handed variant. Child ki has diff --git a/unix/gtkapp.c b/unix/gtkapp.c index 8b2a794fc..351238db8 100644 --- a/unix/gtkapp.c +++ b/unix/gtkapp.c @@ -27,36 +27,27 @@ and you should get unix/PuTTY.app and unix/PTerm.app as output. TODO list for a sensible GTK3 PuTTY/pterm on OS X: -Menu items' keyboard shortcuts (Command-Q for Quit, Command-V for -Paste) do not currently work. It's intentional that if you turn on -'Command key acts as Meta' in the configuration then those shortcuts -should be superseded by the Meta-key functionality (e.g. Cmd-Q should -send ESC Q to the session), for the benefit of people whose non-Mac -keyboard reflexes expect the Meta key to be in that position; but if -you don't turn that option on, then these shortcuts should work as an -ordinary Mac user expects, and currently they don't. - -Windows don't close sensibly when their sessions terminate. This is -because until now I've relied on calling cleanup_exit() or -gtk_main_quit() in gtkwin.c to terminate the program, which is -conceptually wrong in this situation (we don't want to quit the whole -application when just one window closes) and also doesn't reliably -work anyway (GtkApplication doesn't seem to have a gtk_main invocation -in it at all, so those calls to gtk_main_quit produce a GTK assertion -failure message on standard error). Need to introduce a proper 'clean -up this struct gui_data' function (including finalising other stuff -dangling off it like the backend), call that, and delete just that one -window. (And then work out a replacement mechanism for having the -ordinary Unix-style gtkmain.c based programs terminate when their -session does.) connection_fatal() in particular should invoke this -mechanism, and terminate just the connection that had trouble. +Still to do on the application menu bar: items that have to vary with +context or user action (saved sessions and mid-session special +commands), and disabling/enabling the main actions in parallel with +their counterparts in the Ctrl-rightclick context menu. Mouse wheel events and trackpad scrolling gestures don't work quite -right in the terminal drawing area. - -There doesn't seem to be a resize handle on terminal windows. I don't -think this is a fundamental limitation of OS X GTK (their demo app has -one), so perhaps I need to do something to make sure it appears? +right in the terminal drawing area. This seems to be a combination of +two things, neither of which I completely understand yet. Firstly, on +OS X GTK my trackpad seems to generate GDK scroll events for which +gdk_event_get_scroll_deltas returns integers rather than integer +multiples of 1/30, so we end up scrolling by very large amounts; +secondly, the window doesn't seem to receive a GTK "draw" event until +after the entire scroll gesture is complete, which means we don't get +constant visual feedback on how much we're scrolling by. + +There doesn't seem to be a resize handle on terminal windows. Then +again, they do seem to _be_ resizable; the handle just isn't shown. +Perhaps that's a feature (certainly in a scrollbarless configuration +the handle gets in the way of the bottom right character cell in the +terminal itself), but it would be nice to at least understand _why_ it +happens and perhaps include an option to put it back again. A slight oddity with menus that pop up directly under the mouse pointer: mousing over the menu items doesn't highlight them initially, @@ -64,37 +55,6 @@ but if I mouse off the menu and back on (without un-popping-it-up) then suddenly that does work. I don't know if this is something I can fix, though; it might very well be a quirk of the underlying GTK. -I want to arrange *some* way to paste efficiently using my Apple -wireless keyboard and trackpad. The trackpad doesn't provide a middle -button; I can't use the historic Shift-Ins shortcut because the -keyboard has no Ins key; I configure the Command key to be Meta, so -Command-V is off the table too. I can always use the menu, but I'd -prefer there to be _some_ easily reachable mouse or keyboard gesture. - -Revamping the clipboard handling in general is going to be needed, as -well. Not everybody will want the current auto-copy-on-select -behaviour inherited from ordinary Unix PuTTY. Should arrange to have a -mode in which you have to take an explicit Copy action, and then -arrange that the Edit menu includes one of those. - -Dialog boxes shouldn't be modal. I think this is a good policy change -in general, and the required infrastructure changes will benefit the -Windows front end too, but for a multi-session process it's even more -critical - you need to be able to type into one session window while -setting up the configuration for launching another. So everywhere we -currently run a sub-instance of gtk_main, or call any API function -that implicitly does that (like gtk_dialog_run), we should switch to -putting up the dialog box and going back to our ordinary main loop, -and whatever we were going to do after the dialog closed we should -remember to do it when that happens later on. Also then we can remove -the post_main() horror from gtkcomm.c. - -The application menu bar is very minimal at the moment. Should include -all the usual stuff from the Ctrl-right-click menu - saved sessions, -mid-session special commands, Duplicate Session, Change Settings, -Event Log, clear scrollback, reset terminal, about box, anything else -I can think of. - Does OS X have a standard system of online help that I could tie into? Need to work out what if anything we can do with Pageant on OS X. @@ -124,6 +84,7 @@ I suppose I'll have to look into OS X code signing. #define MAY_REFER_TO_GTK_IN_HEADERS #include "putty.h" +#include "gtkmisc.h" char *x_get_default(const char *key) { return NULL; } @@ -135,15 +96,18 @@ const int buildinfo_gtk_relevant = TRUE; * in the source than it is to remove it in the makefile edifice. */ int main(int argc, char **argv) { - fprintf(stderr, "launcher does nothing on non-OSX platforms\n"); + fprintf(stderr, "GtkApplication frontend doesn't work pre-GTK3\n"); return 1; } GtkWidget *make_gtk_toplevel_window(void *frontend) { return NULL; } void launch_duplicate_session(Conf *conf) {} void launch_new_session(void) {} void launch_saved_session(const char *str) {} +void session_window_closed(void) {} #else /* GTK_CHECK_VERSION(3,0,0) */ +extern const int use_event_log; + static void startup(GApplication *app, gpointer user_data) { GMenu *menubar, *menu, *section; @@ -162,21 +126,80 @@ static void startup(GApplication *app, gpointer user_data) section = g_menu_new(); g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + g_menu_append(section, "Copy", "win.copy"); g_menu_append(section, "Paste", "win.paste"); + g_menu_append(section, "Copy All", "win.copyall"); + + menu = g_menu_new(); + g_menu_append_submenu(menubar, "Window", G_MENU_MODEL(menu)); + + section = g_menu_new(); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + g_menu_append(section, "Restart Session", "win.restart"); + g_menu_append(section, "Duplicate Session", "win.duplicate"); + + section = g_menu_new(); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + g_menu_append(section, "Change Settings", "win.changesettings"); + + if (use_event_log) { + section = g_menu_new(); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + g_menu_append(section, "Event Log", "win.eventlog"); + } + + section = g_menu_new(); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + g_menu_append(section, "Clear Scrollback", "win.clearscrollback"); + g_menu_append(section, "Reset Terminal", "win.resetterm"); + +#if GTK_CHECK_VERSION(3,12,0) +#define SET_ACCEL(app, command, accel) do \ + { \ + static const char *const accels[] = { accel, NULL }; \ + gtk_application_set_accels_for_action( \ + GTK_APPLICATION(app), command, accels); \ + } while (0) +#else + /* The Gtk function used above was new in 3.12; the one below + * was deprecated from 3.14. */ +#define SET_ACCEL(app, command, accel) \ + gtk_application_add_accelerator(GTK_APPLICATION(app), accel, \ + command, NULL) +#endif + + SET_ACCEL(app, "app.newwin", "n"); + SET_ACCEL(app, "win.copy", "c"); + SET_ACCEL(app, "win.paste", "v"); + +#undef SET_ACCEL gtk_application_set_menubar(GTK_APPLICATION(app), G_MENU_MODEL(menubar)); } -static void paste_cb(GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - request_paste(user_data); -} +#define WIN_ACTION_LIST(X) \ + X("copy", MA_COPY) \ + X("paste", MA_PASTE) \ + X("copyall", MA_COPY_ALL) \ + X("duplicate", MA_DUPLICATE_SESSION) \ + X("restart", MA_RESTART_SESSION) \ + X("changesettings", MA_CHANGE_SETTINGS) \ + X("clearscrollback", MA_CLEAR_SCROLLBACK) \ + X("resetterm", MA_RESET_TERMINAL) \ + X("eventlog", MA_EVENT_LOG) \ + /* end of list */ + +#define WIN_ACTION_CALLBACK(name, id) \ +static void win_action_cb_ ## id(GSimpleAction *a, GVariant *p, gpointer d) \ +{ app_menu_action(d, id); } +WIN_ACTION_LIST(WIN_ACTION_CALLBACK) +#undef WIN_ACTION_CALLBACK static const GActionEntry win_actions[] = { - { "paste", paste_cb }, +#define WIN_ACTION_ENTRY(name, id) { name, win_action_cb_ ## id }, +WIN_ACTION_LIST(WIN_ACTION_ENTRY) +#undef WIN_ACTION_ENTRY }; static GtkApplication *app; @@ -190,21 +213,28 @@ GtkWidget *make_gtk_toplevel_window(void *frontend) return win; } -extern int cfgbox(Conf *conf); - void launch_duplicate_session(Conf *conf) { extern const int dup_check_launchable; assert(!dup_check_launchable || conf_launchable(conf)); - new_session_window(conf, NULL); + g_application_hold(G_APPLICATION(app)); + new_session_window(conf_copy(conf), NULL); } -void launch_new_session(void) +void session_window_closed(void) { - Conf *conf = conf_new(); - do_defaults(NULL, conf); - if (conf_launchable(conf) || cfgbox(conf)) { + g_application_release(G_APPLICATION(app)); +} + +static void post_initial_config_box(void *vctx, int result) +{ + Conf *conf = (Conf *)vctx; + + if (result > 0) { new_session_window(conf, NULL); + } else if (result == 0) { + conf_free(conf); + g_application_release(G_APPLICATION(app)); } } @@ -212,16 +242,41 @@ void launch_saved_session(const char *str) { Conf *conf = conf_new(); do_defaults(str, conf); - if (conf_launchable(conf) || cfgbox(conf)) { + + g_application_hold(G_APPLICATION(app)); + + if (!conf_launchable(conf)) { + initial_config_box(conf, post_initial_config_box, conf); + } else { new_session_window(conf, NULL); } } +void launch_new_session(void) +{ + /* Same as launch_saved_session except that we pass NULL to + * do_defaults. */ + launch_saved_session(NULL); +} + void new_app_win(GtkApplication *app) { launch_new_session(); } +static void window_setup_error_callback(void *vctx, int result) +{ + g_application_release(G_APPLICATION(app)); +} + +void window_setup_error(const char *errmsg) +{ + create_message_box(NULL, "Error creating session window", errmsg, + string_width("Some sort of fiddly error message that " + "might be technical"), + TRUE, &buttons_ok, window_setup_error_callback, NULL); +} + static void activate(GApplication *app, gpointer user_data) { @@ -242,8 +297,16 @@ static void quit_cb(GSimpleAction *action, g_application_quit(G_APPLICATION(user_data)); } +static void about_cb(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + about_box(NULL); +} + static const GActionEntry app_actions[] = { { "newwin", newwin_cb }, + { "about", about_cb }, { "quit", quit_cb }, }; diff --git a/unix/gtkcols.c b/unix/gtkcols.c index e8223a726..ef060d639 100644 --- a/unix/gtkcols.c +++ b/unix/gtkcols.c @@ -8,6 +8,11 @@ static void columns_init(Columns *cols); static void columns_class_init(ColumnsClass *klass); +#if !GTK_CHECK_VERSION(2,0,0) +static void columns_finalize(GtkObject *object); +#else +static void columns_finalize(GObject *object); +#endif static void columns_map(GtkWidget *widget); static void columns_unmap(GtkWidget *widget); #if !GTK_CHECK_VERSION(2,0,0) @@ -96,11 +101,11 @@ static gint (*columns_inherited_focus)(GtkContainer *container, static void columns_class_init(ColumnsClass *klass) { #if !GTK_CHECK_VERSION(2,0,0) - /* GtkObjectClass *object_class = (GtkObjectClass *)klass; */ + GtkObjectClass *object_class = (GtkObjectClass *)klass; GtkWidgetClass *widget_class = (GtkWidgetClass *)klass; GtkContainerClass *container_class = (GtkContainerClass *)klass; #else - /* GObjectClass *object_class = G_OBJECT_CLASS(klass); */ + GObjectClass *object_class = G_OBJECT_CLASS(klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass); #endif @@ -111,6 +116,7 @@ static void columns_class_init(ColumnsClass *klass) parent_class = g_type_class_peek_parent(klass); #endif + object_class->finalize = columns_finalize; widget_class->map = columns_map; widget_class->unmap = columns_unmap; #if !GTK_CHECK_VERSION(2,0,0) @@ -149,6 +155,50 @@ static void columns_init(Columns *cols) cols->spacing = 0; } +static void columns_child_free(gpointer vchild) +{ + ColumnsChild *child = (ColumnsChild *)vchild; + if (child->percentages) + g_free(child->percentages); + g_free(child); +} + +static void columns_finalize( +#if !GTK_CHECK_VERSION(2,0,0) + GtkObject *object +#else + GObject *object +#endif + ) +{ + Columns *cols; + + g_return_if_fail(object != NULL); + g_return_if_fail(IS_COLUMNS(object)); + + cols = COLUMNS(object); + +#if !GTK_CHECK_VERSION(2,0,0) + { + GList *node; + for (node = cols->children; node; node = node->next) + if (node->data) + columns_child_free(node->data); + } + g_list_free(cols->children); +#else + g_list_free_full(cols->children, columns_child_free); +#endif + + cols->children = NULL; + +#if !GTK_CHECK_VERSION(2,0,0) + GTK_OBJECT_CLASS(parent_class)->finalize(object); +#else + G_OBJECT_CLASS(parent_class)->finalize(object); +#endif +} + /* * These appear to be thoroughly tedious functions; the only reason * we have to reimplement them at all is because we defined our own @@ -406,6 +456,7 @@ void columns_add(Columns *cols, GtkWidget *child, childdata->colspan = colspan; childdata->force_left = FALSE; childdata->same_height_as = NULL; + childdata->percentages = NULL; cols->children = g_list_append(cols->children, childdata); cols->taborder = g_list_append(cols->taborder, child); diff --git a/unix/gtkcomm.c b/unix/gtkcomm.c index 288846549..9c3d1c53a 100644 --- a/unix/gtkcomm.c +++ b/unix/gtkcomm.c @@ -203,40 +203,9 @@ static int idle_fn_scheduled; static void notify_toplevel_callback(void *); -/* - * Replacement code for the gtk_quit_add() function, which GTK2 - in - * their unbounded wisdom - deprecated without providing any usable - * replacement, and which we were using to ensure that our idle - * function for toplevel callbacks was only run from the outermost - * gtk_main(). - * - * We must make sure that all our subsidiary calls to gtk_main() are - * followed by a call to post_main(), so that the idle function can be - * re-established when we end up back at the top level. - */ -void post_main(void) -{ - if (gtk_main_level() == 1) - notify_toplevel_callback(NULL); -} - static gint idle_toplevel_callback_func(gpointer data) { - if (gtk_main_level() > 1) { - /* - * We don't run callbacks if we're in the middle of a - * subsidiary gtk_main. So unschedule this idle function; it - * will be rescheduled by post_main() when we come back up a - * level, which is the earliest we might actually do - * something. - */ - if (idle_fn_scheduled) { /* double-check, just in case */ - g_source_remove(toplevel_callback_idle_id); - idle_fn_scheduled = FALSE; - } - } else { - run_toplevel_callbacks(); - } + run_toplevel_callbacks(); /* * If we've emptied our toplevel callback queue, unschedule diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index f16119488..85e04bb8c 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -92,7 +92,10 @@ struct dlgparam { int nselparams; struct selparam *selparams; #endif + struct controlbox *ctrlbox; int retval; + post_dialog_fn_t after; + void *afterctx; }; #define FLAG_UPDATING_COMBO_LIST 1 #define FLAG_UPDATING_LISTBOX 2 @@ -142,7 +145,10 @@ static void colourchoose_response(GtkDialog *dialog, static void coloursel_ok(GtkButton *button, gpointer data); static void coloursel_cancel(GtkButton *button, gpointer data); #endif +#if !GTK_CHECK_VERSION(3,0,0) static void window_destroy(GtkWidget *widget, gpointer data); +#endif +static void dlgparam_destroy(GtkWidget *widget, gpointer data); int get_listitemheight(GtkWidget *widget); static int uctrl_cmp_byctrl(void *av, void *bv) @@ -1078,52 +1084,17 @@ static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child) #endif } +void trivial_post_dialog_fn(void *vctx, int result) +{ +} + void dlg_error_msg(void *dlg, const char *msg) { struct dlgparam *dp = (struct dlgparam *)dlg; - GtkWidget *window; - -#if GTK_CHECK_VERSION(3,0,0) - window = gtk_message_dialog_new(GTK_WINDOW(dp->window), - (GTK_DIALOG_MODAL | - GTK_DIALOG_DESTROY_WITH_PARENT), - GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, - "%s", msg); - gtk_dialog_run(GTK_DIALOG(window)); - gtk_widget_destroy(window); -#else - GtkWidget *hbox, *text, *ok; - - window = gtk_dialog_new(); - text = gtk_label_new(msg); - align_label_left(GTK_LABEL(text)); - hbox = gtk_hbox_new(FALSE, 0); - gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 20); - gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(window))), - hbox, FALSE, FALSE, 20); - gtk_widget_show(text); - gtk_widget_show(hbox); - gtk_window_set_title(GTK_WINDOW(window), "Error"); - gtk_label_set_line_wrap(GTK_LABEL(text), TRUE); - ok = gtk_button_new_with_label("OK"); - gtk_box_pack_end(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(window))), - ok, FALSE, FALSE, 0); - gtk_widget_show(ok); - gtk_widget_set_can_default(ok, TRUE); - gtk_window_set_default(GTK_WINDOW(window), ok); - g_signal_connect(G_OBJECT(ok), "clicked", - G_CALLBACK(errmsg_button_clicked), window); - g_signal_connect(G_OBJECT(window), "destroy", - G_CALLBACK(window_destroy), NULL); - gtk_window_set_modal(GTK_WINDOW(window), TRUE); - gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(dp->window)); - set_transient_window_pos(dp->window, window); - gtk_widget_show(window); - gtk_main(); -#endif - - post_main(); + create_message_box( + dp->window, "Error", msg, + string_width("Some sort of text about a config-box error message"), + FALSE, &buttons_ok, trivial_post_dialog_fn, NULL); } /* @@ -2615,10 +2586,12 @@ static void treeitem_sel(GtkItem *item, gpointer data) } #endif +#if !GTK_CHECK_VERSION(3,0,0) static void window_destroy(GtkWidget *widget, gpointer data) { gtk_main_quit(); } +#endif #if !GTK_CHECK_VERSION(2,0,0) static int tree_grab_focus(struct dlgparam *dp) @@ -2958,13 +2931,13 @@ void treeview_map_event(GtkWidget *tree, gpointer data) } #endif -int do_config_box(const char *title, Conf *conf, int midsession, - int protcfginfo) +GtkWidget *create_config_box(const char *title, Conf *conf, + int midsession, int protcfginfo, + post_dialog_fn_t after, void *afterctx) { GtkWidget *window, *hbox, *vbox, *cols, *label, *tree, *treescroll, *panels, *panelvbox; int index, level, protocol; - struct controlbox *ctrlbox; char *path; #if GTK_CHECK_VERSION(2,0,0) GtkTreeStore *treestore; @@ -2976,13 +2949,17 @@ int do_config_box(const char *title, Conf *conf, int midsession, GtkTreeItem *treeitemlevels[8]; GtkTree *treelevels[8]; #endif - struct dlgparam dp; + struct dlgparam *dp; struct Shortcuts scs; struct selparam *selparams = NULL; int nselparams = 0, selparamsize = 0; - dlg_init(&dp); + dp = snew(struct dlgparam); + dp->after = after; + dp->afterctx = afterctx; + + dlg_init(dp); for (index = 0; index < lenof(scs.sc); index++) { scs.sc[index].action = SHORTCUT_EMPTY; @@ -2990,11 +2967,11 @@ int do_config_box(const char *title, Conf *conf, int midsession, window = our_dialog_new(); - ctrlbox = ctrl_new_box(); + dp->ctrlbox = ctrl_new_box(); protocol = conf_get_int(conf, CONF_protocol); - setup_config_box(ctrlbox, midsession, protocol, protcfginfo); - unix_setup_config_box(ctrlbox, midsession, protocol); - gtk_setup_config_box(ctrlbox, midsession, window); + setup_config_box(dp->ctrlbox, midsession, protocol, protcfginfo); + unix_setup_config_box(dp->ctrlbox, midsession, protocol); + gtk_setup_config_box(dp->ctrlbox, midsession, window); gtk_window_set_title(GTK_WINDOW(window), title); hbox = gtk_hbox_new(FALSE, 4); @@ -3028,11 +3005,10 @@ int do_config_box(const char *title, Conf *conf, int midsession, tree = gtk_tree_new(); gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM); gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE); - g_signal_connect(G_OBJECT(tree), "focus", - G_CALLBACK(tree_focus), &dp); + g_signal_connect(G_OBJECT(tree), "focus", G_CALLBACK(tree_focus), dp); #endif g_signal_connect(G_OBJECT(tree), "focus_in_event", - G_CALLBACK(widget_focus), &dp); + G_CALLBACK(widget_focus), dp); shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree); gtk_widget_show(treescroll); gtk_box_pack_start(GTK_BOX(vbox), treescroll, TRUE, TRUE, 0); @@ -3045,12 +3021,12 @@ int do_config_box(const char *title, Conf *conf, int midsession, panelvbox = NULL; path = NULL; level = 0; - for (index = 0; index < ctrlbox->nctrlsets; index++) { - struct controlset *s = ctrlbox->ctrlsets[index]; + for (index = 0; index < dp->ctrlbox->nctrlsets; index++) { + struct controlset *s = dp->ctrlbox->ctrlsets[index]; GtkWidget *w; if (!*s->pathname) { - w = layout_ctrls(&dp, &scs, s, GTK_WINDOW(window)); + w = layout_ctrls(dp, &scs, s, GTK_WINDOW(window)); our_dialog_set_action_area(GTK_WINDOW(window), w); } else { @@ -3104,7 +3080,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, selparams = sresize(selparams, selparamsize, struct selparam); } - selparams[nselparams].dp = &dp; + selparams[nselparams].dp = dp; selparams[nselparams].panels = GTK_NOTEBOOK(panels); selparams[nselparams].panel = panelvbox; selparams[nselparams].shortcuts = scs; /* structure copy */ @@ -3164,9 +3140,9 @@ int do_config_box(const char *title, Conf *conf, int midsession, treelevels[j] = NULL; g_signal_connect(G_OBJECT(treeitem), "key_press_event", - G_CALLBACK(tree_key_press), &dp); + G_CALLBACK(tree_key_press), dp); g_signal_connect(G_OBJECT(treeitem), "focus_in_event", - G_CALLBACK(widget_focus), &dp); + G_CALLBACK(widget_focus), dp); gtk_widget_show(treeitem); @@ -3179,7 +3155,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, nselparams++; } - w = layout_ctrls(&dp, &selparams[nselparams-1].shortcuts, s, NULL); + w = layout_ctrls(dp, &selparams[nselparams-1].shortcuts, s, NULL); gtk_box_pack_start(GTK_BOX(panelvbox), w, FALSE, FALSE, 0); gtk_widget_show(w); } @@ -3201,8 +3177,8 @@ int do_config_box(const char *title, Conf *conf, int midsession, * enough to have all branches expanded without further resizing. */ - dp.nselparams = nselparams; - dp.selparams = selparams; + dp->nselparams = nselparams; + dp->selparams = selparams; #if !GTK_CHECK_VERSION(3,0,0) { @@ -3211,7 +3187,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, */ GtkRequisition req; gtk_widget_size_request(tree, &req); - initial_treeview_collapse(&dp, tree); + initial_treeview_collapse(dp, tree); gtk_widget_set_size_request(tree, req.width, -1); } #else @@ -3220,7 +3196,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, * mapped, because the size computation won't have been done yet. */ g_signal_connect(G_OBJECT(tree), "map", - G_CALLBACK(treeview_map_event), &dp); + G_CALLBACK(treeview_map_event), dp); #endif /* GTK 2 vs 3 */ #endif /* GTK 2+ vs 1 */ @@ -3228,26 +3204,26 @@ int do_config_box(const char *title, Conf *conf, int midsession, g_signal_connect(G_OBJECT(treeselection), "changed", G_CALLBACK(treeselection_changed), selparams); #else - dp.ntreeitems = nselparams; - dp.treeitems = snewn(dp.ntreeitems, GtkWidget *); + dp->ntreeitems = nselparams; + dp->treeitems = snewn(dp->ntreeitems, GtkWidget *); for (index = 0; index < nselparams; index++) { g_signal_connect(G_OBJECT(selparams[index].treeitem), "select", G_CALLBACK(treeitem_sel), &selparams[index]); - dp.treeitems[index] = selparams[index].treeitem; + dp->treeitems[index] = selparams[index].treeitem; } #endif - dp.data = conf; - dlg_refresh(NULL, &dp); + dp->data = conf; + dlg_refresh(NULL, dp); - dp.shortcuts = &selparams[0].shortcuts; + dp->shortcuts = &selparams[0].shortcuts; #if !GTK_CHECK_VERSION(2,0,0) - dp.currtreeitem = dp.treeitems[0]; + dp->currtreeitem = dp->treeitems[0]; #endif - dp.lastfocus = NULL; - dp.retval = 0; - dp.window = window; + dp->lastfocus = NULL; + dp->retval = -1; + dp->window = window; { /* in gtkwin.c */ @@ -3274,8 +3250,8 @@ int do_config_box(const char *title, Conf *conf, int midsession, /* * Set focus into the first available control. */ - for (index = 0; index < ctrlbox->nctrlsets; index++) { - struct controlset *s = ctrlbox->ctrlsets[index]; + for (index = 0; index < dp->ctrlbox->nctrlsets; index++) { + struct controlset *s = dp->ctrlbox->ctrlsets[index]; int done = 0; int j; @@ -3284,8 +3260,8 @@ int do_config_box(const char *title, Conf *conf, int midsession, if (s->ctrls[j]->generic.type != CTRL_TABDELAY && s->ctrls[j]->generic.type != CTRL_COLUMNS && s->ctrls[j]->generic.type != CTRL_TEXT) { - dlg_set_focus(s->ctrls[j], &dp); - dp.lastfocus = s->ctrls[j]; + dlg_set_focus(s->ctrls[j], dp); + dp->lastfocus = s->ctrls[j]; done = 1; break; } @@ -3295,18 +3271,29 @@ int do_config_box(const char *title, Conf *conf, int midsession, } g_signal_connect(G_OBJECT(window), "destroy", - G_CALLBACK(window_destroy), NULL); + G_CALLBACK(dlgparam_destroy), dp); g_signal_connect(G_OBJECT(window), "key_press_event", - G_CALLBACK(win_key_press), &dp); - - gtk_main(); - post_main(); + G_CALLBACK(win_key_press), dp); - dlg_cleanup(&dp); - sfree(selparams); - ctrl_free_box(ctrlbox); + return window; +} - return dp.retval; +static void dlgparam_destroy(GtkWidget *widget, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + dp->after(dp->afterctx, dp->retval); + dlg_cleanup(dp); + ctrl_free_box(dp->ctrlbox); +#if GTK_CHECK_VERSION(2,0,0) + if (dp->selparams) { + int i; + for (i = 0; i < dp->nselparams; i++) + if (dp->selparams[i].treepath) + gtk_tree_path_free(dp->selparams[i].treepath); + sfree(dp->selparams); + } +#endif + sfree(dp); } static void messagebox_handler(union control *ctrl, void *dlg, @@ -3315,66 +3302,73 @@ static void messagebox_handler(union control *ctrl, void *dlg, if (event == EVENT_ACTION) dlg_end(dlg, ctrl->generic.context.i); } -int messagebox(GtkWidget *parentwin, const char *title, const char *msg, - int minwid, int selectable, ...) + +const struct message_box_button button_array_yn[] = { + {"Yes", 'y', +1, 1}, + {"No", 'n', -1, 0}, +}; +const struct message_box_buttons buttons_yn = { + button_array_yn, lenof(button_array_yn), +}; +const struct message_box_button button_array_ok[] = { + {"OK", 'o', 1, 1}, +}; +const struct message_box_buttons buttons_ok = { + button_array_ok, lenof(button_array_ok), +}; + +GtkWidget *create_message_box( + GtkWidget *parentwin, const char *title, const char *msg, int minwid, + int selectable, const struct message_box_buttons *buttons, + post_dialog_fn_t after, void *afterctx) { GtkWidget *window, *w0, *w1; - struct controlbox *ctrlbox; struct controlset *s0, *s1; union control *c, *textctrl; - struct dlgparam dp; + struct dlgparam *dp; struct Shortcuts scs; - int index, ncols, min_type; - va_list ap; + int i, index, ncols, min_type; + + dp = snew(struct dlgparam); + dp->after = after; + dp->afterctx = afterctx; - dlg_init(&dp); + dlg_init(dp); for (index = 0; index < lenof(scs.sc); index++) { scs.sc[index].action = SHORTCUT_EMPTY; } - ctrlbox = ctrl_new_box(); + dp->ctrlbox = ctrl_new_box(); /* - * Preliminary pass over the va_list, to count up the number of - * buttons and find out what kinds there are. + * Count up the number of buttons and find out what kinds there + * are. */ ncols = 0; - va_start(ap, selectable); min_type = +1; - while (va_arg(ap, char *) != NULL) { - int type; - - (void) va_arg(ap, int); /* shortcut */ - type = va_arg(ap, int); /* normal/default/cancel */ - (void) va_arg(ap, int); /* end value */ - + for (i = 0; i < buttons->nbuttons; i++) { + const struct message_box_button *button = &buttons->buttons[i]; ncols++; - if (min_type > type) - min_type = type; + if (min_type > button->type) + min_type = button->type; + assert(button->value >= 0); /* <0 means no return value available */ } - va_end(ap); - s0 = ctrl_getset(ctrlbox, "", "", ""); + s0 = ctrl_getset(dp->ctrlbox, "", "", ""); c = ctrl_columns(s0, 2, 50, 50); c->columns.ncols = s0->ncolumns = ncols; c->columns.percentages = sresize(c->columns.percentages, ncols, int); for (index = 0; index < ncols; index++) c->columns.percentages[index] = (index+1)*100/ncols - index*100/ncols; - va_start(ap, selectable); index = 0; - while (1) { - char *title = va_arg(ap, char *); - int shortcut, type, value; - if (title == NULL) - break; - shortcut = va_arg(ap, int); - type = va_arg(ap, int); - value = va_arg(ap, int); - c = ctrl_pushbutton(s0, title, shortcut, HELPCTX(no_help), - messagebox_handler, I(value)); + for (i = 0; i < buttons->nbuttons; i++) { + const struct message_box_button *button = &buttons->buttons[i]; + c = ctrl_pushbutton(s0, button->title, button->shortcut, + HELPCTX(no_help), messagebox_handler, + I(button->value)); c->generic.column = index++; - if (type > 0) + if (button->type > 0) c->button.isdefault = TRUE; /* We always arrange that _some_ button is labelled as @@ -3386,33 +3380,32 @@ int messagebox(GtkWidget *parentwin, const char *title, const char *msg, * no will be picked, and if there's only one option (a box * that really is just showing a _message_ and not even asking * a question) then that will be picked. */ - if (type == min_type) + if (button->type == min_type) c->button.iscancel = TRUE; } - va_end(ap); - s1 = ctrl_getset(ctrlbox, "x", "", ""); + s1 = ctrl_getset(dp->ctrlbox, "x", "", ""); textctrl = ctrl_text(s1, msg, HELPCTX(no_help)); window = our_dialog_new(); gtk_window_set_title(GTK_WINDOW(window), title); - w0 = layout_ctrls(&dp, &scs, s0, GTK_WINDOW(window)); + w0 = layout_ctrls(dp, &scs, s0, GTK_WINDOW(window)); our_dialog_set_action_area(GTK_WINDOW(window), w0); gtk_widget_show(w0); - w1 = layout_ctrls(&dp, &scs, s1, GTK_WINDOW(window)); + w1 = layout_ctrls(dp, &scs, s1, GTK_WINDOW(window)); gtk_container_set_border_width(GTK_CONTAINER(w1), 10); gtk_widget_set_size_request(w1, minwid+20, -1); our_dialog_add_to_content_area(GTK_WINDOW(window), w1, TRUE, TRUE, 0); gtk_widget_show(w1); - dp.shortcuts = &scs; - dp.lastfocus = NULL; - dp.retval = 0; - dp.window = window; + dp->shortcuts = &scs; + dp->lastfocus = NULL; + dp->retval = 0; + dp->window = window; if (selectable) { #if GTK_CHECK_VERSION(2,0,0) - struct uctrl *uc = dlg_find_byctrl(&dp, textctrl); + struct uctrl *uc = dlg_find_byctrl(dp, textctrl); gtk_label_set_selectable(GTK_LABEL(uc->text), TRUE); /* @@ -3430,7 +3423,6 @@ int messagebox(GtkWidget *parentwin, const char *title, const char *msg, #endif } - gtk_window_set_modal(GTK_WINDOW(window), TRUE); if (parentwin) { set_transient_window_pos(parentwin, window); gtk_window_set_transient_for(GTK_WINDOW(window), @@ -3441,32 +3433,62 @@ int messagebox(GtkWidget *parentwin, const char *title, const char *msg, gtk_widget_show(window); gtk_window_set_focus(GTK_WINDOW(window), NULL); + dp->selparams = NULL; + g_signal_connect(G_OBJECT(window), "destroy", - G_CALLBACK(window_destroy), NULL); + G_CALLBACK(dlgparam_destroy), dp); g_signal_connect(G_OBJECT(window), "key_press_event", - G_CALLBACK(win_key_press), &dp); - - gtk_main(); - post_main(); - - dlg_cleanup(&dp); - ctrl_free_box(ctrlbox); + G_CALLBACK(win_key_press), dp); - return dp.retval; + return window; } -int reallyclose(void *frontend) +struct verify_ssh_host_key_result_ctx { + char *host; + int port; + char *keytype; + char *keystr; + void (*callback)(void *callback_ctx, int result); + void *callback_ctx; + void *frontend; +}; + +static void verify_ssh_host_key_result_callback(void *vctx, int result) { - char *title = dupcat(appname, " Exit Confirmation", NULL); - int ret = messagebox(GTK_WIDGET(get_window(frontend)), - title, "Are you sure you want to close this session?", - string_width("Most of the width of the above text"), - FALSE, - "Yes", 'y', +1, 1, - "No", 'n', -1, 0, - NULL); - sfree(title); - return ret; + struct verify_ssh_host_key_result_ctx *ctx = + (struct verify_ssh_host_key_result_ctx *)vctx; + + if (result >= 0) { + int logical_result; + + /* + * Convert the dialog-box return value (one of three + * possibilities) into the return value we pass back to the SSH + * code (one of only two possibilities, because the SSH code + * doesn't care whether we saved the host key or not). + */ + if (result == 2) { + store_host_key(ctx->host, ctx->port, ctx->keytype, ctx->keystr); + logical_result = 1; /* continue with connection */ + } else if (result == 1) { + logical_result = 1; /* continue with connection */ + } else { + logical_result = 0; /* do not continue with connection */ + } + + ctx->callback(ctx->callback_ctx, logical_result); + } + + /* + * Clean up this context structure, whether or not a result was + * ever actually delivered from the dialog box. + */ + unregister_dialog(ctx->frontend, DIALOG_SLOT_NETWORK_PROMPT); + + sfree(ctx->host); + sfree(ctx->keytype); + sfree(ctx->keystr); + sfree(ctx); } int verify_ssh_host_key(void *frontend, char *host, int port, @@ -3499,8 +3521,19 @@ int verify_ssh_host_key(void *frontend, char *host, int port, "If you want to abandon the connection completely, press " "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed " "safe choice."; + static const struct message_box_button button_array_hostkey[] = { + {"Accept", 'a', 0, 2}, + {"Connect Once", 'o', 0, 1}, + {"Cancel", 'c', -1, 0}, + }; + static const struct message_box_buttons buttons_hostkey = { + button_array_hostkey, lenof(button_array_hostkey), + }; + char *text; int ret; + struct verify_ssh_host_key_result_ctx *result_ctx; + GtkWidget *mainwin, *msgbox; /* * Verify the key. @@ -3512,23 +3545,47 @@ int verify_ssh_host_key(void *frontend, char *host, int port, text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint); - ret = messagebox(GTK_WIDGET(get_window(frontend)), - "PuTTY Security Alert", text, - string_width(fingerprint), - TRUE, - "Accept", 'a', 0, 2, - "Connect Once", 'o', 0, 1, - "Cancel", 'c', -1, 0, - NULL); + result_ctx = snew(struct verify_ssh_host_key_result_ctx); + result_ctx->callback = callback; + result_ctx->callback_ctx = ctx; + result_ctx->host = dupstr(host); + result_ctx->port = port; + result_ctx->keytype = dupstr(keytype); + result_ctx->keystr = dupstr(keystr); + result_ctx->frontend = frontend; + + mainwin = GTK_WIDGET(get_window(frontend)); + msgbox = create_message_box( + mainwin, "PuTTY Security Alert", text, string_width(fingerprint), TRUE, + &buttons_hostkey, verify_ssh_host_key_result_callback, result_ctx); + register_dialog(frontend, DIALOG_SLOT_NETWORK_PROMPT, msgbox); sfree(text); - if (ret == 2) { - store_host_key(host, port, keytype, keystr); - return 1; /* continue with connection */ - } else if (ret == 1) - return 1; /* continue with connection */ - return 0; /* do not continue with connection */ + return -1; /* dialog still in progress */ +} + +struct simple_prompt_result_ctx { + void (*callback)(void *callback_ctx, int result); + void *callback_ctx; + void *frontend; + enum DialogSlot dialog_slot; +}; + +static void simple_prompt_result_callback(void *vctx, int result) +{ + struct simple_prompt_result_ctx *ctx = + (struct simple_prompt_result_ctx *)vctx; + + if (result >= 0) + ctx->callback(ctx->callback_ctx, result); + + /* + * Clean up this context structure, whether or not a result was + * ever actually delivered from the dialog box. + */ + unregister_dialog(ctx->frontend, ctx->dialog_slot); + sfree(ctx); } /* @@ -3542,25 +3599,29 @@ int askalg(void *frontend, const char *algtype, const char *algname, "The first %s supported by the server is " "%s, which is below the configured warning threshold.\n" "Continue with connection?"; + char *text; - int ret; + struct simple_prompt_result_ctx *result_ctx; + GtkWidget *mainwin, *msgbox; text = dupprintf(msg, algtype, algname); - ret = messagebox(GTK_WIDGET(get_window(frontend)), - "PuTTY Security Alert", text, - string_width("Reasonably long line of text as a width" - " template"), - FALSE, - "Yes", 'y', 0, 1, - "No", 'n', 0, 0, - NULL); + + result_ctx = snew(struct simple_prompt_result_ctx); + result_ctx->callback = callback; + result_ctx->callback_ctx = ctx; + result_ctx->frontend = frontend; + result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT; + + mainwin = GTK_WIDGET(get_window(frontend)); + msgbox = create_message_box( + mainwin, "PuTTY Security Alert", text, + string_width("Reasonably long line of text as a width template"), + FALSE, &buttons_yn, simple_prompt_result_callback, result_ctx); + register_dialog(frontend, result_ctx->dialog_slot, msgbox); + sfree(text); - if (ret) { - return 1; - } else { - return 0; - } + return -1; /* dialog still in progress */ } int askhk(void *frontend, const char *algname, const char *betteralgs, @@ -3573,25 +3634,30 @@ int askhk(void *frontend, const char *algname, const char *betteralgs, "above the threshold, which we do not have stored:\n" "%s\n" "Continue with connection?"; + char *text; - int ret; + struct simple_prompt_result_ctx *result_ctx; + GtkWidget *mainwin, *msgbox; text = dupprintf(msg, algname, betteralgs); - ret = messagebox(GTK_WIDGET(get_window(frontend)), - "PuTTY Security Alert", text, - string_width("is ecdsa-nistp521, which is" - " below the configured warning threshold."), - FALSE, - "Yes", 'y', 0, 1, - "No", 'n', 0, 0, - NULL); + + result_ctx = snew(struct simple_prompt_result_ctx); + result_ctx->callback = callback; + result_ctx->callback_ctx = ctx; + result_ctx->frontend = frontend; + result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT; + + mainwin = GTK_WIDGET(get_window(frontend)); + msgbox = create_message_box( + mainwin, "PuTTY Security Alert", text, + string_width("is ecdsa-nistp521, which is below the configured" + " warning threshold."), + FALSE, &buttons_yn, simple_prompt_result_callback, result_ctx); + register_dialog(frontend, result_ctx->dialog_slot, msgbox); + sfree(text); - if (ret) { - return 1; - } else { - return 0; - } + return -1; /* dialog still in progress */ } void old_keyfile_warning(void) @@ -3601,30 +3667,14 @@ void old_keyfile_warning(void) */ } -void fatal_message_box(void *window, const char *msg) -{ - messagebox(window, "PuTTY Fatal Error", msg, - string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), - FALSE, "OK", 'o', 1, 1, NULL); -} - void nonfatal_message_box(void *window, const char *msg) { - messagebox(window, "PuTTY Error", msg, - string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), - FALSE, "OK", 'o', 1, 1, NULL); -} - -void fatalbox(const char *p, ...) -{ - va_list ap; - char *msg; - va_start(ap, p); - msg = dupvprintf(p, ap); - va_end(ap); - fatal_message_box(NULL, msg); - sfree(msg); - cleanup_exit(1); + char *title = dupcat(appname, " Error", NULL); + create_message_box( + window, title, msg, + string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), + FALSE, &buttons_ok, trivial_post_dialog_fn, NULL); + sfree(title); } void nonfatal(const char *p, ...) @@ -3661,10 +3711,10 @@ static void licence_clicked(GtkButton *button, gpointer data) title = dupcat(appname, " Licence", NULL); assert(aboutbox != NULL); - messagebox(aboutbox, title, LICENCE_TEXT("\n\n"), - string_width("LONGISH LINE OF TEXT SO THE LICENCE" - " BOX ISN'T EXCESSIVELY TALL AND THIN"), - TRUE, "OK", 'o', 1, 1, NULL); + create_message_box(aboutbox, title, LICENCE_TEXT("\n\n"), + string_width("LONGISH LINE OF TEXT SO THE LICENCE" + " BOX ISN'T EXCESSIVELY TALL AND THIN"), + TRUE, &buttons_ok, trivial_post_dialog_fn, NULL); sfree(title); } @@ -3717,7 +3767,8 @@ void about_box(void *window) our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, FALSE, FALSE, 0); #if GTK_CHECK_VERSION(2,0,0) /* - * Same precautions against initial select-all as in messagebox(). + * Same precautions against initial select-all as in + * create_message_box(). */ gtk_widget_grab_focus(w); gtk_label_select_region(GTK_LABEL(w), 0, 0); @@ -3728,21 +3779,26 @@ void about_box(void *window) G_CALLBACK(about_key_press), NULL); set_transient_window_pos(GTK_WIDGET(window), aboutbox); - gtk_window_set_transient_for(GTK_WINDOW(aboutbox), - GTK_WINDOW(window)); + if (window) + gtk_window_set_transient_for(GTK_WINDOW(aboutbox), + GTK_WINDOW(window)); gtk_container_set_focus_child(GTK_CONTAINER(aboutbox), NULL); gtk_widget_show(aboutbox); gtk_window_set_focus(GTK_WINDOW(aboutbox), NULL); } +#define LOGEVENT_INITIAL_MAX 128 +#define LOGEVENT_CIRCULAR_MAX 128 + struct eventlog_stuff { GtkWidget *parentwin, *window; struct controlbox *eventbox; struct Shortcuts scs; struct dlgparam dp; union control *listctrl; - char **events; - int nevents, negsize; + char **events_initial; + char **events_circular; + int ninitial, ncircular, circular_first; char *seldata; int sellen; int ignore_selchange; @@ -3774,8 +3830,11 @@ static void eventlog_list_handler(union control *ctrl, void *dlg, dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); - for (i = 0; i < es->nevents; i++) { - dlg_listbox_add(ctrl, dlg, es->events[i]); + for (i = 0; i < es->ninitial; i++) { + dlg_listbox_add(ctrl, dlg, es->events_initial[i]); + } + for (i = 0; i < es->ncircular; i++) { + dlg_listbox_add(ctrl, dlg, es->events_circular[(es->circular_first + i) % LOGEVENT_CIRCULAR_MAX]); } dlg_update_done(ctrl, dlg); } else if (event == EVENT_SELCHANGE) { @@ -3797,16 +3856,30 @@ static void eventlog_list_handler(union control *ctrl, void *dlg, sfree(es->seldata); es->seldata = NULL; es->sellen = 0; - for (i = 0; i < es->nevents; i++) { + for (i = 0; i < es->ninitial; i++) { if (dlg_listbox_issel(ctrl, dlg, i)) { - int extralen = strlen(es->events[i]); + int extralen = strlen(es->events_initial[i]); if (es->sellen + extralen + 2 > selsize) { selsize = es->sellen + extralen + 512; es->seldata = sresize(es->seldata, selsize, char); } - strcpy(es->seldata + es->sellen, es->events[i]); + strcpy(es->seldata + es->sellen, es->events_initial[i]); + es->sellen += extralen; + es->seldata[es->sellen++] = '\n'; + } + } + for (i = 0; i < es->ncircular; i++) { + if (dlg_listbox_issel(ctrl, dlg, ninitial + i)) { + int extralen = strlen(es->events_circular[(es->circular_first + i) % LOGEVENT_CIRCULAR_MAX]); + + if (es->sellen + extralen + 2 > selsize) { + selsize = es->sellen + extralen + 512; + es->seldata = sresize(es->seldata, selsize, char); + } + + strcpy(es->seldata + es->sellen, es->events_circular[i]); es->sellen += extralen; es->seldata[es->sellen++] = '\n'; } @@ -3952,28 +4025,53 @@ void *eventlogstuff_new(void) return es; } +#define MAXLOGMSGLEN 1000 + void logevent_dlg(void *estuff, const char *string) { struct eventlog_stuff *es = (struct eventlog_stuff *)estuff; - char timebuf[40]; struct tm tm; - - if (es->nevents >= es->negsize) { - es->negsize += 64; - es->events = sresize(es->events, es->negsize, char *); + char **location; + size_t i; + size_t slen = strlen(string); + size_t len = (slen > MAXLOGMSGLEN) ? MAXLOGMSGLEN : slen; + + if (es->ninitial == 0) { + es->events_initial = sresize(es->events_initial, LOGEVENT_INITIAL_MAX, char *); + for (i = 0; i < LOGEVENT_INITIAL_MAX; i++) + es->events_initial[i] = NULL; + es->events_circular = sresize(es->events_circular, LOGEVENT_CIRCULAR_MAX, char *); + for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++) + es->events_circular[i] = NULL; } + if (es->ninitial < LOGEVENT_INITIAL_MAX) + location = &es->events_initial[es->ninitial]; + else + location = &es->events_circular[(es->circular_first + es->ncircular) % LOGEVENT_CIRCULAR_MAX]; + tm=ltime(); strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm); - es->events[es->nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char); - strcpy(es->events[es->nevents], timebuf); - strcat(es->events[es->nevents], string); + sfree(*location); + *location = snewn(strlen(timebuf) + len + 1, char); + strcpy(*location, timebuf); + strncat(*location, string, len); if (es->window) { - dlg_listbox_add(es->listctrl, &es->dp, es->events[es->nevents]); + dlg_listbox_add(es->listctrl, &es->dp, *location); + } + if (es->ninitial < LOGEVENT_INITIAL_MAX) { + es->ninitial++; + } else if (es->ncircular < LOGEVENT_CIRCULAR_MAX) { + es->ncircular++; + } else if (es->ncircular == LOGEVENT_CIRCULAR_MAX) { + es->circular_first = (es->circular_first + 1) % LOGEVENT_CIRCULAR_MAX; + sfree(es->events_circular[es->circular_first]); + es->events_circular[es->circular_first] = snewn(sizeof(".."), char); + strcpy(es->events_circular[es->circular_first], ".."); + es->events_circular[es->circular_first][sizeof("..") - 1] = '\0'; } - es->nevents++; } int askappend(void *frontend, Filename *filename, @@ -3984,24 +4082,38 @@ int askappend(void *frontend, Filename *filename, "You can overwrite it with a new session log, " "append your session log to the end of it, " "or disable session logging for this session."; + static const struct message_box_button button_array_append[] = { + {"Overwrite", 'o', 1, 2}, + {"Append", 'a', 0, 1}, + {"Disable", 'd', -1, 0}, + }; + static const struct message_box_buttons buttons_append = { + button_array_append, lenof(button_array_append), + }; + char *message; char *mbtitle; - int mbret; + struct simple_prompt_result_ctx *result_ctx; + GtkWidget *mainwin, *msgbox; message = dupprintf(msgtemplate, FILENAME_MAX, filename->path); mbtitle = dupprintf("%s Log to File", appname); - mbret = messagebox(get_window(frontend), mbtitle, message, - string_width("LINE OF TEXT SUITABLE FOR THE" - " ASKAPPEND WIDTH"), - FALSE, - "Overwrite", 'o', 1, 2, - "Append", 'a', 0, 1, - "Disable", 'd', -1, 0, - NULL); + result_ctx = snew(struct simple_prompt_result_ctx); + result_ctx->callback = callback; + result_ctx->callback_ctx = ctx; + result_ctx->frontend = frontend; + result_ctx->dialog_slot = DIALOG_SLOT_LOGFILE_PROMPT; + + mainwin = GTK_WIDGET(get_window(frontend)); + msgbox = create_message_box( + mainwin, mbtitle, message, + string_width("LINE OF TEXT SUITABLE FOR THE ASKAPPEND WIDTH"), + FALSE, &buttons_append, simple_prompt_result_callback, result_ctx); + register_dialog(frontend, result_ctx->dialog_slot, msgbox); sfree(message); sfree(mbtitle); - return mbret; + return -1; /* dialog still in progress */ } diff --git a/unix/gtkmain.c b/unix/gtkmain.c index c80da702a..675816048 100644 --- a/unix/gtkmain.c +++ b/unix/gtkmain.c @@ -296,12 +296,26 @@ static void version(FILE *fp) { sfree(buildinfo_text); } -static struct gui_data *the_inst; - static const char *geometry_string; -int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, - Conf *conf) +void cmdline_error(const char *p, ...) +{ + va_list ap; + fprintf(stderr, "%s: ", appname); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +void window_setup_error(const char *errmsg) +{ + fprintf(stderr, "%s: %s\n", appname, errmsg); + exit(1); +} + +int do_cmdline(int argc, char **argv, int do_everything, Conf *conf) { int err = 0; char *val; @@ -521,10 +535,13 @@ int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, pgp_fingerprints(); exit(1); - } else if(p[0] != '-' && (!do_everything || - process_nonoption_arg(p, conf, - allow_launch))) { - /* do nothing */ + } else if (p[0] != '-') { + /* Non-option arguments not handled by cmdline.c are errors. */ + if (do_everything) { + err = 1; + fprintf(stderr, "%s: unexpected non-option argument '%s'\n", + appname, p); + } } else { err = 1; @@ -540,10 +557,35 @@ GtkWidget *make_gtk_toplevel_window(void *frontend) return gtk_window_new(GTK_WINDOW_TOPLEVEL); } -extern int cfgbox(Conf *conf); - const int buildinfo_gtk_relevant = TRUE; +struct post_initial_config_box_ctx { + Conf *conf; + const char *geometry_string; +}; + +static void post_initial_config_box(void *vctx, int result) +{ + struct post_initial_config_box_ctx ctx = + *(struct post_initial_config_box_ctx *)vctx; + sfree(vctx); + + if (result > 0) { + new_session_window(ctx.conf, ctx.geometry_string); + } else if (result == 0) { + /* In this main(), which only runs one session in total, a + * negative result from the initial config box means we simply + * terminate. */ + conf_free(ctx.conf); + gtk_main_quit(); + } +} + +void session_window_closed(void) +{ + gtk_main_quit(); +} + int main(int argc, char **argv) { Conf *conf; @@ -596,39 +638,42 @@ int main(int argc, char **argv) assert(!dup_check_launchable || conf_launchable(conf)); need_config_box = FALSE; } else { - /* By default, we bring up the config dialog, rather than launching - * a session. This gets set to TRUE if something happens to change - * that (e.g., a hostname is specified on the command-line). */ - int allow_launch = FALSE; - if (do_cmdline(argc, argv, 0, &allow_launch, conf)) + if (do_cmdline(argc, argv, 0, conf)) exit(1); /* pre-defaults pass to get -class */ do_defaults(NULL, conf); - if (do_cmdline(argc, argv, 1, &allow_launch, conf)) + if (do_cmdline(argc, argv, 1, conf)) exit(1); /* post-defaults, do everything */ cmdline_run_saved(conf); - if (loaded_session) - allow_launch = TRUE; - - need_config_box = (!allow_launch || !conf_launchable(conf)); + if (cmdline_tooltype & TOOLTYPE_HOST_ARG) + need_config_box = !cmdline_host_ok(conf); + else + need_config_box = FALSE; } - /* - * Put up the config box. - */ - if (need_config_box && !cfgbox(conf)) - exit(0); /* config box hit Cancel */ - - /* - * Create the main session window. We don't really need to keep - * the return value - the fact that it'll be linked from a zillion - * GTK and glib bits and bobs known to the main loop will be - * sufficient to make everything actually happen - but we stash it - * in a global variable anyway, so that it'll be easy to find in a - * debugger. - */ - the_inst = new_session_window(conf, geometry_string); + if (need_config_box) { + /* + * Put up the initial config box, which will pass the provided + * parameters (with conf updated) to new_session_window() when + * (if) the user selects Open. Or it might close without + * creating a session window, if the user selects Cancel. Or + * it might just create the session window immediately if this + * is a pterm-style app which doesn't have an initial config + * box at all. + */ + struct post_initial_config_box_ctx *ctx = + snew(struct post_initial_config_box_ctx); + ctx->conf = conf; + ctx->geometry_string = geometry_string; + initial_config_box(conf, post_initial_config_box, ctx); + } else { + /* + * No initial config needed; just create the session window + * now. + */ + new_session_window(conf, geometry_string); + } gtk_main(); diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 69e335094..5cfd5f448 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -52,7 +52,36 @@ GdkAtom compound_text_atom, utf8_string_atom; -struct clipboard_data_instance; +#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 +/* + * Because calling gtk_clipboard_set_with_data triggers a call to the + * clipboard_clear function from the last time, we need to arrange a + * way to distinguish a real call to clipboard_clear for the _new_ + * instance of the clipboard data from the leftover call for the + * outgoing one. We do this by setting the user data field in our + * gtk_clipboard_set_with_data() call, instead of the obvious pointer + * to 'inst', to one of these. + */ +struct clipboard_data_instance { + char *pasteout_data_utf8; + int pasteout_data_utf8_len; + struct clipboard_state *state; + struct clipboard_data_instance *next, *prev; +}; +#endif + +struct clipboard_state { + struct gui_data *inst; + int clipboard; + GdkAtom atom; +#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 + GtkClipboard *gtkclipboard; + struct clipboard_data_instance *current_cdi; +#else + char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8; + int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len; +#endif +}; struct gui_data { GtkWidget *window, *area, *sbar; @@ -101,15 +130,14 @@ struct gui_data { GdkColormap *colmap; #endif int direct_to_font; - wchar_t *pastein_data; - int pastein_data_len; + struct clipboard_state clipstates[N_CLIPBOARDS]; #ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - GtkClipboard *clipboard; - struct clipboard_data_instance *current_cdi; -#else - char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8; - int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len; + /* Remember all clipboard_data_instance structures currently + * associated with this gui_data, in case they're still around + * when it gets destroyed */ + struct clipboard_data_instance cdi_headtail; #endif + int clipboard_ctrlshiftins, clipboard_ctrlshiftcv; int font_width, font_height; int width, height; int ignore_sbar; @@ -130,7 +158,7 @@ struct gui_data { Conf *conf; void *eventlogstuff; guint32 input_event_time; /* Timestamp of the most recent input event. */ - int reconfiguring; + GtkWidget *dialogs[DIALOG_SLOT_LIMIT]; #if GTK_CHECK_VERSION(3,4,0) gdouble cumulative_scroll; #endif @@ -140,6 +168,9 @@ struct gui_data { int cursor_type; int drawtype; int meta_mod_mask; +#ifdef OSX_META_KEY_CONFIG + int system_mod_mask; +#endif }; static void cache_conf_values(struct gui_data *inst) @@ -153,6 +184,7 @@ static void cache_conf_values(struct gui_data *inst) inst->meta_mod_mask |= GDK_MOD1_MASK; if (conf_get_int(inst->conf, CONF_osx_command_meta)) inst->meta_mod_mask |= GDK_MOD2_MASK; + inst->system_mod_mask = GDK_MOD2_MASK & ~inst->meta_mod_mask; #else inst->meta_mod_mask = GDK_MOD1_MASK; #endif @@ -167,6 +199,38 @@ static int send_raw_mouse; static void start_backend(struct gui_data *inst); static void exit_callback(void *vinst); +static void destroy_inst_connection(struct gui_data *inst); +static void delete_inst(struct gui_data *inst); + +static void post_fatal_message_box_toplevel(void *vctx) +{ + struct gui_data *inst = (struct gui_data *)vctx; + gtk_widget_destroy(inst->window); +} + +static void post_fatal_message_box(void *vctx, int result) +{ + struct gui_data *inst = (struct gui_data *)vctx; + unregister_dialog(inst, DIALOG_SLOT_CONNECTION_FATAL); + queue_toplevel_callback(post_fatal_message_box_toplevel, inst); +} + +void fatal_message_box(struct gui_data *inst, const char *msg) +{ + char *title = dupcat(appname, " Fatal Error", NULL); + GtkWidget *dialog = create_message_box( + inst->window, title, msg, + string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), + FALSE, &buttons_ok, post_fatal_message_box, inst); + register_dialog(inst, DIALOG_SLOT_CONNECTION_FATAL, dialog); + sfree(title); +} + +static void connection_fatal_callback(void *vinst) +{ + struct gui_data *inst = (struct gui_data *)vinst; + destroy_inst_connection(inst); +} void connection_fatal(void *frontend, const char *p, ...) { @@ -177,10 +241,11 @@ void connection_fatal(void *frontend, const char *p, ...) va_start(ap, p); msg = dupvprintf(p, ap); va_end(ap); - fatal_message_box(inst->window, msg); + fatal_message_box(inst, msg); sfree(msg); - queue_toplevel_callback(exit_callback, inst); + inst->exited = TRUE; /* suppress normal exit handling */ + queue_toplevel_callback(connection_fatal_callback, frontend); } /* @@ -301,12 +366,32 @@ static Mouse_Button translate_button(Mouse_Button button) * Return the top-level GtkWindow associated with a particular * front end instance. */ -void *get_window(void *frontend) +GtkWidget *get_window(void *frontend) { struct gui_data *inst = (struct gui_data *)frontend; return inst->window; } +/* + * Set and clear a pointer to a dialog box created as a result of the + * network code wanting to ask an asynchronous user question (e.g. + * 'what about this dodgy host key, then?'). + */ +void register_dialog(void *frontend, enum DialogSlot slot, GtkWidget *dialog) +{ + struct gui_data *inst = (struct gui_data *)frontend; + assert(slot < DIALOG_SLOT_LIMIT); + assert(!inst->dialogs[slot]); + inst->dialogs[slot] = dialog; +} +void unregister_dialog(void *frontend, enum DialogSlot slot) +{ + struct gui_data *inst = (struct gui_data *)frontend; + assert(slot < DIALOG_SLOT_LIMIT); + assert(inst->dialogs[slot]); + inst->dialogs[slot] = NULL; +} + /* * Minimise or restore the window in response to a server-side * request. @@ -430,6 +515,25 @@ void get_window_pixels(void *frontend, int *x, int *y) #endif } +/* + * Find out whether a dialog box already exists for this window in a + * particular DialogSlot. If it does, uniconify it (if we can) and + * raise it, so that the user realises they've already been asked this + * question. + */ +static int find_and_raise_dialog(struct gui_data *inst, enum DialogSlot slot) +{ + GtkWidget *dialog = inst->dialogs[slot]; + if (!dialog) + return FALSE; + +#if GTK_CHECK_VERSION(2,0,0) + gtk_window_deiconify(GTK_WINDOW(dialog)); +#endif + gdk_window_raise(gtk_widget_get_window(dialog)); + return TRUE; +} + /* * Return the window or icon title. */ @@ -439,12 +543,44 @@ char *get_window_title(void *frontend, int icon) return icon ? inst->icontitle : inst->wintitle; } +static void warn_on_close_callback(void *vctx, int result) +{ + struct gui_data *inst = (struct gui_data *)vctx; + unregister_dialog(inst, DIALOG_SLOT_WARN_ON_CLOSE); + if (result) + gtk_widget_destroy(inst->window); +} + +/* + * Handle the 'delete window' event (e.g. user clicking the WM close + * button). The return value FALSE means the window should close, and + * TRUE means it shouldn't. + * + * (That's counterintuitive, but really, in GTK terms, TRUE means 'I + * have done everything necessary to handle this event, so the default + * handler need not do anything', i.e. 'suppress default handler', + * i.e. 'do not close the window'.) + */ gint delete_window(GtkWidget *widget, GdkEvent *event, gpointer data) { struct gui_data *inst = (struct gui_data *)data; if (!inst->exited && conf_get_int(inst->conf, CONF_warn_on_close)) { - if (!reallyclose(inst)) - return TRUE; + /* + * We're not going to exit right now. We must put up a + * warn-on-close dialog, unless one already exists, in which + * case we'll just re-emphasise that one. + */ + if (!find_and_raise_dialog(inst, DIALOG_SLOT_WARN_ON_CLOSE)) { + char *title = dupcat(appname, " Exit Confirmation", NULL); + GtkWidget *dialog = create_message_box( + inst->window, title, + "Are you sure you want to close this session?", + string_width("Most of the width of the above text"), + FALSE, &buttons_yn, warn_on_close_callback, inst); + register_dialog(inst, DIALOG_SLOT_WARN_ON_CLOSE, dialog); + sfree(title); + } + return TRUE; } return FALSE; } @@ -648,6 +784,11 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) int ucsval, start, end, special, output_charset, use_ucsoutput; int nethack_mode, app_keypad_mode; +#ifdef OSX_META_KEY_CONFIG + if (event->state & inst->system_mod_mask) + return FALSE; /* let GTK process OS X Command key */ +#endif + /* Remember the timestamp. */ inst->input_event_time = event->time; @@ -861,6 +1002,15 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) * Shift-PgUp and Shift-PgDn don't even generate keystrokes * at all. */ + if (event->keyval == GDK_KEY_Page_Up && + ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == + (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Shift-PgUp scroll\n")); +#endif + term_scroll(inst->term, 1, 0); + return TRUE; + } if (event->keyval == GDK_KEY_Page_Up && (event->state & GDK_SHIFT_MASK)) { #ifdef KEY_EVENT_DIAGNOSTICS @@ -877,6 +1027,15 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) term_scroll(inst->term, 0, -1); return TRUE; } + if (event->keyval == GDK_KEY_Page_Down && + ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == + (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-shift-PgDn scroll\n")); +#endif + term_scroll(inst->term, -1, 0); + return TRUE; + } if (event->keyval == GDK_KEY_Page_Down && (event->state & GDK_SHIFT_MASK)) { #ifdef KEY_EVENT_DIAGNOSTICS @@ -895,15 +1054,126 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) } /* - * Neither does Shift-Ins. + * Neither do Shift-Ins or Ctrl-Ins (if enabled). */ if (event->keyval == GDK_KEY_Insert && (event->state & GDK_SHIFT_MASK)) { + int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins); + + switch (cfgval) { + case CLIPUI_IMPLICIT: #ifdef KEY_EVENT_DIAGNOSTICS - debug((" - Shift-Insert paste\n")); + debug((" - Shift-Insert: paste from PRIMARY\n")); #endif - request_paste(inst); - return TRUE; + term_request_paste(inst->term, CLIP_PRIMARY); + return TRUE; + case CLIPUI_EXPLICIT: +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Shift-Insert: paste from CLIPBOARD\n")); +#endif + term_request_paste(inst->term, CLIP_CLIPBOARD); + return TRUE; + case CLIPUI_CUSTOM: +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Shift-Insert: paste from custom clipboard\n")); +#endif + term_request_paste(inst->term, inst->clipboard_ctrlshiftins); + return TRUE; + default: +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Shift-Insert: no paste action\n")); +#endif + break; + } + } + if (event->keyval == GDK_KEY_Insert && + (event->state & GDK_CONTROL_MASK)) { + static const int clips_clipboard[] = { CLIP_CLIPBOARD }; + int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins); + + switch (cfgval) { + case CLIPUI_IMPLICIT: + /* do nothing; re-copy to PRIMARY is not needed */ +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Insert: non-copy to PRIMARY\n")); +#endif + return TRUE; + case CLIPUI_EXPLICIT: +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Insert: copy to CLIPBOARD\n")); +#endif + term_request_copy(inst->term, + clips_clipboard, lenof(clips_clipboard)); + return TRUE; + case CLIPUI_CUSTOM: +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Insert: copy to custom clipboard\n")); +#endif + term_request_copy(inst->term, + &inst->clipboard_ctrlshiftins, 1); + return TRUE; + default: +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Insert: no copy action\n")); +#endif + break; + } + } + + /* + * Another pair of copy-paste keys. + */ + if ((event->state & GDK_SHIFT_MASK) && + (event->state & GDK_CONTROL_MASK) && + (event->keyval == GDK_KEY_C || event->keyval == GDK_KEY_c || + event->keyval == GDK_KEY_V || event->keyval == GDK_KEY_v)) { + int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftcv); + int paste = (event->keyval == GDK_KEY_V || + event->keyval == GDK_KEY_v); + + switch (cfgval) { + case CLIPUI_IMPLICIT: + if (paste) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Shift-V: paste from PRIMARY\n")); +#endif + term_request_paste(inst->term, CLIP_PRIMARY); + } else { +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Shift-C: non-copy to PRIMARY\n")); +#endif + } + return TRUE; + case CLIPUI_EXPLICIT: + if (paste) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Shift-V: paste from CLIPBOARD\n")); +#endif + term_request_paste(inst->term, CLIP_CLIPBOARD); + } else { + static const int clips[] = { CLIP_CLIPBOARD }; +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Shift-C: copy to CLIPBOARD\n")); +#endif + term_request_copy(inst->term, clips, lenof(clips)); + } + return TRUE; + case CLIPUI_CUSTOM: + if (paste) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Shift-V: paste from custom clipboard\n")); +#endif + term_request_paste(inst->term, + inst->clipboard_ctrlshiftcv); + } else { +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Shift-C: copy to custom clipboard\n")); +#endif + term_request_copy(inst->term, + &inst->clipboard_ctrlshiftcv, 1); + } + return TRUE; + } } special = FALSE; @@ -1923,7 +2193,7 @@ gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) return FALSE; event_button = (GdkEventButton *)gdk_event_new(GDK_BUTTON_PRESS); - event_button->window = event->window; + event_button->window = g_object_ref(event->window); event_button->send_event = event->send_event; event_button->time = event->time; event_button->x = event->x; @@ -1931,11 +2201,11 @@ gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) event_button->axes = NULL; event_button->state = event->state; event_button->button = button; - event_button->device = event->device; + event_button->device = g_object_ref(event->device); event_button->x_root = event->x_root; event_button->y_root = event->y_root; ret = button_internal(inst, event_button); - gdk_event_free(event_button); + gdk_event_free((GdkEvent *)event_button); return ret; #endif } @@ -1981,7 +2251,7 @@ void frontend_keypress(void *handle) * any keypress. */ if (inst->exited) - cleanup_exit(0); + gtk_widget_destroy(inst->window); } static void exit_callback(void *vinst) @@ -1991,34 +2261,108 @@ static void exit_callback(void *vinst) if (!inst->exited && (exitcode = inst->back->exitcode(inst->backhandle)) >= 0) { - inst->exited = TRUE; + destroy_inst_connection(inst); + close_on_exit = conf_get_int(inst->conf, CONF_close_on_exit); if (close_on_exit == FORCE_ON || - (close_on_exit == AUTO && exitcode == 0)) - gtk_main_quit(); /* just go */ - if (inst->ldisc) { - ldisc_free(inst->ldisc); - inst->ldisc = NULL; - } + (close_on_exit == AUTO && exitcode == 0)) { + gtk_widget_destroy(inst->window); + } + } +} + +void notify_remote_exit(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + queue_toplevel_callback(exit_callback, inst); +} + +static void destroy_inst_connection(struct gui_data *inst) +{ + inst->exited = TRUE; + if (inst->ldisc) { + ldisc_free(inst->ldisc); + inst->ldisc = NULL; + } + if (inst->backhandle) { inst->back->free(inst->backhandle); inst->backhandle = NULL; inst->back = NULL; + } + if (inst->term) term_provide_resize_fn(inst->term, NULL, NULL); + if (inst->menu) { update_specials_menu(inst); - gtk_widget_set_sensitive(inst->restartitem, TRUE); + gtk_widget_set_sensitive(inst->restartitem, TRUE); } } -void notify_remote_exit(void *frontend) +static void delete_inst(struct gui_data *inst) { - struct gui_data *inst = (struct gui_data *)frontend; + int dialog_slot; + for (dialog_slot = 0; dialog_slot < DIALOG_SLOT_LIMIT; dialog_slot++) { + if (inst->dialogs[dialog_slot]) { + gtk_widget_destroy(inst->dialogs[dialog_slot]); + inst->dialogs[dialog_slot] = NULL; + } + } + if (inst->window) { + gtk_widget_destroy(inst->window); + inst->window = NULL; + } + if (inst->menu) { + gtk_widget_destroy(inst->menu); + inst->menu = NULL; + } + destroy_inst_connection(inst); + if (inst->term) { + term_free(inst->term); + inst->term = NULL; + } + if (inst->conf) { + conf_free(inst->conf); + inst->conf = NULL; + } + if (inst->logctx) { + log_free(inst->logctx); + inst->logctx = NULL; + } - queue_toplevel_callback(exit_callback, inst); +#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 + /* + * Clear up any in-flight clipboard_data_instances. We can't + * actually _free_ them, but we detach them from the inst that's + * about to be destroyed. + */ + while (inst->cdi_headtail.next != &inst->cdi_headtail) { + struct clipboard_data_instance *cdi = inst->cdi_headtail.next; + cdi->state = NULL; + cdi->next->prev = cdi->prev; + cdi->prev->next = cdi->next; + cdi->next = cdi->prev = cdi; + } +#endif + + /* + * Delete any top-level callbacks associated with inst, which + * would otherwise become stale-pointer dereferences waiting to + * happen. We do this last, because some of the above cleanups + * (notably shutting down the backend) might themelves queue such + * callbacks, so we need to make sure they don't do that _after_ + * we're supposed to have cleaned everything up. + */ + delete_callbacks_for_context(inst); + + sfree(inst); } void destroy(GtkWidget *widget, gpointer data) { - gtk_main_quit(); + struct gui_data *inst = (struct gui_data *)data; + inst->window = NULL; + delete_inst(inst); + session_window_closed(); } gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data) @@ -2188,11 +2532,11 @@ void set_gtk_widget_background(GtkWidget *widget, const GdkColor *col) free(data); free(col_css); #else - if (gtk_widget_get_window(win)) { + if (gtk_widget_get_window(widget)) { /* For GTK1, which doesn't have a 'const' on * gdk_window_set_background's second parameter type. */ GdkColor col_mutable = *col; - gdk_window_set_background(gtk_widget_get_window(win), &col_mutable); + gdk_window_set_background(gtk_widget_get_window(widget), &col_mutable); } #endif } @@ -2222,6 +2566,17 @@ void palette_set(void *frontend, int n, int r, int g, int b) } } +int palette_get(void *frontend, int n, int *r, int *g, int *b) +{ + struct gui_data *inst = (struct gui_data *)frontend; + if (n < 0 || n >= NALLCOLOURS) + return FALSE; + *r = inst->cols[n].red >> 8; + *g = inst->cols[n].green >> 8; + *b = inst->cols[n].blue >> 8; + return TRUE; +} + void palette_reset(void *frontend) { struct gui_data *inst = (struct gui_data *)frontend; @@ -2291,6 +2646,20 @@ void palette_reset(void *frontend) } } +static struct clipboard_state *clipboard_from_atom( + struct gui_data *inst, GdkAtom atom) +{ + int i; + + for (i = 0; i < N_CLIPBOARDS; i++) { + struct clipboard_state *state = &inst->clipstates[i]; + if (state->inst == inst && state->atom == atom) + return state; + } + + return NULL; +} + #ifdef JUST_USE_GTK_CLIPBOARD_UTF8 /* ---------------------------------------------------------------------- @@ -2300,26 +2669,29 @@ void palette_reset(void *frontend) * formats it feels like. */ -void init_clipboard(struct gui_data *inst) +void set_clipboard_atom(struct gui_data *inst, int clipboard, GdkAtom atom) { - inst->clipboard = gtk_clipboard_get_for_display(gdk_display_get_default(), - DEFAULT_CLIPBOARD); + struct clipboard_state *state = &inst->clipstates[clipboard]; + + state->inst = inst; + state->clipboard = clipboard; + state->atom = atom; + + if (state->atom != GDK_NONE) { + state->gtkclipboard = gtk_clipboard_get_for_display( + gdk_display_get_default(), state->atom); + g_object_set_data(G_OBJECT(state->gtkclipboard), "user-data", state); + } else { + state->gtkclipboard = NULL; + } } -/* - * Because calling gtk_clipboard_set_with_data triggers a call to the - * clipboard_clear function from the last time, we need to arrange a - * way to distinguish a real call to clipboard_clear for the _new_ - * instance of the clipboard data from the leftover call for the - * outgoing one. We do this by setting the user data field in our - * gtk_clipboard_set_with_data() call, instead of the obvious pointer - * to 'inst', to one of these. - */ -struct clipboard_data_instance { - struct gui_data *inst; - char *pasteout_data_utf8; - int pasteout_data_utf8_len; -}; +int init_clipboard(struct gui_data *inst) +{ + set_clipboard_atom(inst, CLIP_PRIMARY, GDK_SELECTION_PRIMARY); + set_clipboard_atom(inst, CLIP_CLIPBOARD, GDK_SELECTION_CLIPBOARD); + return TRUE; +} static void clipboard_provide_data(GtkClipboard *clipboard, GtkSelectionData *selection_data, @@ -2327,9 +2699,8 @@ static void clipboard_provide_data(GtkClipboard *clipboard, { struct clipboard_data_instance *cdi = (struct clipboard_data_instance *)data; - struct gui_data *inst = cdi->inst; - if (inst->current_cdi == cdi) { + if (cdi->state && cdi->state->current_cdi == cdi) { gtk_selection_data_set_text(selection_data, cdi->pasteout_data_utf8, cdi->pasteout_data_utf8_len); } @@ -2339,20 +2710,26 @@ static void clipboard_clear(GtkClipboard *clipboard, gpointer data) { struct clipboard_data_instance *cdi = (struct clipboard_data_instance *)data; - struct gui_data *inst = cdi->inst; - if (inst->current_cdi == cdi) { - term_deselect(inst->term); - inst->current_cdi = NULL; + if (cdi->state && cdi->state->current_cdi == cdi) { + if (cdi->state->inst && cdi->state->inst->term) { + term_lost_clipboard_ownership(cdi->state->inst->term, + cdi->state->clipboard); + } + cdi->state->current_cdi = NULL; } sfree(cdi->pasteout_data_utf8); + cdi->next->prev = cdi->prev; + cdi->prev->next = cdi->next; sfree(cdi); } -void write_clip(void *frontend, wchar_t *data, int *attr, int len, +void write_clip(void *frontend, int clipboard, + wchar_t *data, int *attr, truecolour *truecolour, int len, int must_deselect) { struct gui_data *inst = (struct gui_data *)frontend; + struct clipboard_state *state = &inst->clipstates[clipboard]; struct clipboard_data_instance *cdi; if (inst->direct_to_font) { @@ -2364,10 +2741,17 @@ void write_clip(void *frontend, wchar_t *data, int *attr, int len, return; } + if (!state->gtkclipboard) + return; + cdi = snew(struct clipboard_data_instance); - cdi->inst = inst; - inst->current_cdi = cdi; + cdi->state = state; + state->current_cdi = cdi; cdi->pasteout_data_utf8 = snewn(len*6, char); + cdi->prev = inst->cdi_headtail.prev; + cdi->next = &inst->cdi_headtail; + cdi->next->prev = cdi; + cdi->prev->next = cdi; { const wchar_t *tmp = data; int tmplen = len; @@ -2390,9 +2774,9 @@ void write_clip(void *frontend, wchar_t *data, int *attr, int len, targetlist = gtk_target_list_new(NULL, 0); gtk_target_list_add_text_targets(targetlist, 0); targettable = gtk_target_table_new_from_list(targetlist, &n_targets); - gtk_clipboard_set_with_data(inst->clipboard, targettable, n_targets, - clipboard_provide_data, clipboard_clear, - cdi); + gtk_clipboard_set_with_data(state->gtkclipboard, targettable, + n_targets, clipboard_provide_data, + clipboard_clear, cdi); gtk_target_table_free(targettable, n_targets); gtk_target_list_unref(targetlist); } @@ -2402,6 +2786,8 @@ static void clipboard_text_received(GtkClipboard *clipboard, const gchar *text, gpointer data) { struct gui_data *inst = (struct gui_data *)data; + wchar_t *paste; + int paste_len; int length; if (!text) @@ -2409,20 +2795,24 @@ static void clipboard_text_received(GtkClipboard *clipboard, length = strlen(text); - if (inst->pastein_data) - sfree(inst->pastein_data); + paste = snewn(length, wchar_t); + paste_len = mb_to_wc(CS_UTF8, 0, text, length, paste, length); - inst->pastein_data = snewn(length, wchar_t); - inst->pastein_data_len = mb_to_wc(CS_UTF8, 0, text, length, - inst->pastein_data, length); + term_do_paste(inst->term, paste, paste_len); - term_do_paste(inst->term); + sfree(paste); } -void request_paste(void *frontend) +void frontend_request_paste(void *frontend, int clipboard) { struct gui_data *inst = (struct gui_data *)frontend; - gtk_clipboard_request_text(inst->clipboard, clipboard_text_received, inst); + struct clipboard_state *state = &inst->clipstates[clipboard]; + + if (!state->gtkclipboard) + return; + + gtk_clipboard_request_text(state->gtkclipboard, + clipboard_text_received, inst); } #else /* JUST_USE_GTK_CLIPBOARD_UTF8 */ @@ -2477,16 +2867,19 @@ static char *retrieve_cutbuffer(int *nbytes) #endif } -void write_clip(void *frontend, wchar_t *data, int *attr, int len, +void write_clip(void *frontend, int clipboard, + wchar_t *data, int *attr, truecolour *truecolour, int len, int must_deselect) { struct gui_data *inst = (struct gui_data *)frontend; - if (inst->pasteout_data) - sfree(inst->pasteout_data); - if (inst->pasteout_data_ctext) - sfree(inst->pasteout_data_ctext); - if (inst->pasteout_data_utf8) - sfree(inst->pasteout_data_utf8); + struct clipboard_state *state = &inst->clipstates[clipboard]; + + if (state->pasteout_data) + sfree(state->pasteout_data); + if (state->pasteout_data_ctext) + sfree(state->pasteout_data_ctext); + if (state->pasteout_data_utf8) + sfree(state->pasteout_data_utf8); /* * Set up UTF-8 and compound text paste data. This only happens @@ -2501,79 +2894,81 @@ void write_clip(void *frontend, wchar_t *data, int *attr, int len, Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); #endif - inst->pasteout_data_utf8 = snewn(len*6, char); - inst->pasteout_data_utf8_len = len*6; - inst->pasteout_data_utf8_len = - charset_from_unicode(&tmp, &tmplen, inst->pasteout_data_utf8, - inst->pasteout_data_utf8_len, + state->pasteout_data_utf8 = snewn(len*6, char); + state->pasteout_data_utf8_len = len*6; + state->pasteout_data_utf8_len = + charset_from_unicode(&tmp, &tmplen, state->pasteout_data_utf8, + state->pasteout_data_utf8_len, CS_UTF8, NULL, NULL, 0); - if (inst->pasteout_data_utf8_len == 0) { - sfree(inst->pasteout_data_utf8); - inst->pasteout_data_utf8 = NULL; + if (state->pasteout_data_utf8_len == 0) { + sfree(state->pasteout_data_utf8); + state->pasteout_data_utf8 = NULL; } else { - inst->pasteout_data_utf8 = - sresize(inst->pasteout_data_utf8, - inst->pasteout_data_utf8_len + 1, char); - inst->pasteout_data_utf8[inst->pasteout_data_utf8_len] = '\0'; + state->pasteout_data_utf8 = + sresize(state->pasteout_data_utf8, + state->pasteout_data_utf8_len + 1, char); + state->pasteout_data_utf8[state->pasteout_data_utf8_len] = '\0'; } /* * Now let Xlib convert our UTF-8 data into compound text. */ #ifndef NOT_X_WINDOWS - list[0] = inst->pasteout_data_utf8; + list[0] = state->pasteout_data_utf8; if (Xutf8TextListToTextProperty(disp, list, 1, XCompoundTextStyle, &tp) == 0) { - inst->pasteout_data_ctext = snewn(tp.nitems+1, char); - memcpy(inst->pasteout_data_ctext, tp.value, tp.nitems); - inst->pasteout_data_ctext_len = tp.nitems; + state->pasteout_data_ctext = snewn(tp.nitems+1, char); + memcpy(state->pasteout_data_ctext, tp.value, tp.nitems); + state->pasteout_data_ctext_len = tp.nitems; XFree(tp.value); } else #endif { - inst->pasteout_data_ctext = NULL; - inst->pasteout_data_ctext_len = 0; + state->pasteout_data_ctext = NULL; + state->pasteout_data_ctext_len = 0; } } else { - inst->pasteout_data_utf8 = NULL; - inst->pasteout_data_utf8_len = 0; - inst->pasteout_data_ctext = NULL; - inst->pasteout_data_ctext_len = 0; + state->pasteout_data_utf8 = NULL; + state->pasteout_data_utf8_len = 0; + state->pasteout_data_ctext = NULL; + state->pasteout_data_ctext_len = 0; } - inst->pasteout_data = snewn(len*6, char); - inst->pasteout_data_len = len*6; - inst->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, - data, len, inst->pasteout_data, - inst->pasteout_data_len, + state->pasteout_data = snewn(len*6, char); + state->pasteout_data_len = len*6; + state->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, + data, len, state->pasteout_data, + state->pasteout_data_len, NULL, NULL, NULL); - if (inst->pasteout_data_len == 0) { - sfree(inst->pasteout_data); - inst->pasteout_data = NULL; + if (state->pasteout_data_len == 0) { + sfree(state->pasteout_data); + state->pasteout_data = NULL; } else { - inst->pasteout_data = - sresize(inst->pasteout_data, inst->pasteout_data_len, char); + state->pasteout_data = + sresize(state->pasteout_data, state->pasteout_data_len, char); } - store_cutbuffer(inst->pasteout_data, inst->pasteout_data_len); + /* The legacy X cut buffers go with PRIMARY, not any other clipboard */ + if (state->atom == GDK_SELECTION_PRIMARY) + store_cutbuffer(state->pasteout_data, state->pasteout_data_len); - if (gtk_selection_owner_set(inst->area, GDK_SELECTION_PRIMARY, + if (gtk_selection_owner_set(inst->area, state->atom, inst->input_event_time)) { #if GTK_CHECK_VERSION(2,0,0) - gtk_selection_clear_targets(inst->area, GDK_SELECTION_PRIMARY); + gtk_selection_clear_targets(inst->area, state->atom); #endif - gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + gtk_selection_add_target(inst->area, state->atom, GDK_SELECTION_TYPE_STRING, 1); - if (inst->pasteout_data_ctext) - gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + if (state->pasteout_data_ctext) + gtk_selection_add_target(inst->area, state->atom, compound_text_atom, 1); - if (inst->pasteout_data_utf8) - gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + if (state->pasteout_data_utf8) + gtk_selection_add_target(inst->area, state->atom, utf8_string_atom, 1); } if (must_deselect) - term_deselect(inst->term); + term_lost_clipboard_ownership(inst->term, clipboard); } static void selection_get(GtkWidget *widget, GtkSelectionData *seldata, @@ -2581,44 +2976,57 @@ static void selection_get(GtkWidget *widget, GtkSelectionData *seldata, { struct gui_data *inst = (struct gui_data *)data; GdkAtom target = gtk_selection_data_get_target(seldata); + struct clipboard_state *state = clipboard_from_atom( + inst, gtk_selection_data_get_selection(seldata)); + + if (!state) + return; + if (target == utf8_string_atom) gtk_selection_data_set(seldata, target, 8, - (unsigned char *)inst->pasteout_data_utf8, - inst->pasteout_data_utf8_len); + (unsigned char *)state->pasteout_data_utf8, + state->pasteout_data_utf8_len); else if (target == compound_text_atom) gtk_selection_data_set(seldata, target, 8, - (unsigned char *)inst->pasteout_data_ctext, - inst->pasteout_data_ctext_len); + (unsigned char *)state->pasteout_data_ctext, + state->pasteout_data_ctext_len); else gtk_selection_data_set(seldata, target, 8, - (unsigned char *)inst->pasteout_data, - inst->pasteout_data_len); + (unsigned char *)state->pasteout_data, + state->pasteout_data_len); } static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata, gpointer data) { struct gui_data *inst = (struct gui_data *)data; + struct clipboard_state *state = clipboard_from_atom( + inst, seldata->selection); + + if (!state) + return TRUE; - term_deselect(inst->term); - if (inst->pasteout_data) - sfree(inst->pasteout_data); - if (inst->pasteout_data_ctext) - sfree(inst->pasteout_data_ctext); - if (inst->pasteout_data_utf8) - sfree(inst->pasteout_data_utf8); - inst->pasteout_data = NULL; - inst->pasteout_data_len = 0; - inst->pasteout_data_ctext = NULL; - inst->pasteout_data_ctext_len = 0; - inst->pasteout_data_utf8 = NULL; - inst->pasteout_data_utf8_len = 0; + term_lost_clipboard_ownership(inst->term, state->clipboard); + if (state->pasteout_data) + sfree(state->pasteout_data); + if (state->pasteout_data_ctext) + sfree(state->pasteout_data_ctext); + if (state->pasteout_data_utf8) + sfree(state->pasteout_data_utf8); + state->pasteout_data = NULL; + state->pasteout_data_len = 0; + state->pasteout_data_ctext = NULL; + state->pasteout_data_ctext_len = 0; + state->pasteout_data_utf8 = NULL; + state->pasteout_data_utf8_len = 0; return TRUE; } -void request_paste(void *frontend) +void frontend_request_paste(void *frontend, int clipboard) { struct gui_data *inst = (struct gui_data *)frontend; + struct clipboard_state *state = &inst->clipstates[clipboard]; + /* * In Unix, pasting is asynchronous: all we can do at the * moment is to call gtk_selection_convert(), and when the data @@ -2633,17 +3041,16 @@ void request_paste(void *frontend) * fails, selection_received() will be informed and will * fall back to an ordinary string. */ - gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, - utf8_string_atom, + gtk_selection_convert(inst->area, state->atom, utf8_string_atom, inst->input_event_time); } else { /* * If we're in direct-to-font mode, we disable UTF-8 * pasting, and go straight to ordinary string data. */ - gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, - GDK_SELECTION_TYPE_STRING, - inst->input_event_time); + gtk_selection_convert(inst->area, state->atom, + GDK_SELECTION_TYPE_STRING, + inst->input_event_time); } } @@ -2663,13 +3070,20 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, GdkAtom seldata_type = gtk_selection_data_get_data_type(seldata); const guchar *seldata_data = gtk_selection_data_get_data(seldata); gint seldata_length = gtk_selection_data_get_length(seldata); + wchar_t *paste; + int paste_len; + struct clipboard_state *state = clipboard_from_atom( + inst, gtk_selection_data_get_selection(seldata)); + + if (!state) + return; if (seldata_target == utf8_string_atom && seldata_length <= 0) { /* * Failed to get a UTF-8 selection string. Try compound * text next. */ - gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + gtk_selection_convert(inst->area, state->atom, compound_text_atom, inst->input_event_time); return; @@ -2680,7 +3094,7 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, * Failed to get UTF-8 or compound text. Try an ordinary * string. */ - gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + gtk_selection_convert(inst->area, state->atom, GDK_SELECTION_TYPE_STRING, inst->input_event_time); return; @@ -2737,7 +3151,7 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, /* * Compound text failed; fall back to STRING. */ - gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + gtk_selection_convert(inst->area, state->atom, GDK_SELECTION_TYPE_STRING, inst->input_event_time); return; @@ -2750,16 +3164,12 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, } } - if (inst->pastein_data) - sfree(inst->pastein_data); + paste = snewn(length, wchar_t); + paste_len = mb_to_wc(charset, 0, text, length, paste, length); - inst->pastein_data = snewn(length, wchar_t); - inst->pastein_data_len = length; - inst->pastein_data_len = - mb_to_wc(charset, 0, text, length, - inst->pastein_data, inst->pastein_data_len); + term_do_paste(inst->term, paste, paste_len); - term_do_paste(inst->term); + sfree(paste); #ifndef NOT_X_WINDOWS if (free_list_required) @@ -2769,6 +3179,24 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, #endif } +static void init_one_clipboard(struct gui_data *inst, int clipboard) +{ + struct clipboard_state *state = &inst->clipstates[clipboard]; + + state->inst = inst; + state->clipboard = clipboard; +} + +void set_clipboard_atom(struct gui_data *inst, int clipboard, GdkAtom atom) +{ + struct clipboard_state *state = &inst->clipstates[clipboard]; + + state->inst = inst; + state->clipboard = clipboard; + + state->atom = atom; +} + void init_clipboard(struct gui_data *inst) { #ifndef NOT_X_WINDOWS @@ -2804,6 +3232,11 @@ void init_clipboard(struct gui_data *inst) XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, empty, 0); #endif + inst->clipstates[CLIP_PRIMARY].atom = GDK_SELECTION_PRIMARY; + inst->clipstates[CLIP_CLIPBOARD].atom = GDK_SELECTION_CLIPBOARD; + init_one_clipboard(inst, CLIP_PRIMARY); + init_one_clipboard(inst, CLIP_CLIPBOARD); + g_signal_connect(G_OBJECT(inst->area), "selection_received", G_CALLBACK(selection_received), inst); g_signal_connect(G_OBJECT(inst->area), "selection_get", @@ -2819,16 +3252,6 @@ void init_clipboard(struct gui_data *inst) #endif /* JUST_USE_GTK_CLIPBOARD_UTF8 */ -void get_clip(void *frontend, wchar_t ** p, int *len) -{ - struct gui_data *inst = (struct gui_data *)frontend; - - if (p) { - *p = inst->pastein_data; - *len = inst->pastein_data_len; - } -} - static void set_window_titles(struct gui_data *inst) { /* @@ -2873,13 +3296,13 @@ void set_sbar(void *frontend, int total, int start, int page) struct gui_data *inst = (struct gui_data *)frontend; if (!conf_get_int(inst->conf, CONF_scrollbar)) return; + inst->ignore_sbar = TRUE; gtk_adjustment_set_lower(inst->sbar_adjust, 0); gtk_adjustment_set_upper(inst->sbar_adjust, total); gtk_adjustment_set_value(inst->sbar_adjust, start); gtk_adjustment_set_page_size(inst->sbar_adjust, page); gtk_adjustment_set_step_increment(inst->sbar_adjust, 1); gtk_adjustment_set_page_increment(inst->sbar_adjust, page/2); - inst->ignore_sbar = TRUE; #if !GTK_CHECK_VERSION(3,18,0) gtk_adjustment_changed(inst->sbar_adjust); #endif @@ -3012,19 +3435,73 @@ static void draw_update(struct draw_ctx *dctx, int x, int y, int w, int h) gtk_widget_queue_draw_area(dctx->inst->area, x, y, w, h); } -static void draw_set_colour(struct draw_ctx *dctx, int col) +#ifdef DRAW_TEXT_CAIRO +static void cairo_set_source_rgb_dim(cairo_t *cr, double r, double g, double b, + int dim) +{ + if (dim) + cairo_set_source_rgb(cr, r * 2 / 3, g * 2 / 3, b * 2 / 3); + else + cairo_set_source_rgb(cr, r, g, b); +} +#endif + +static void draw_set_colour(struct draw_ctx *dctx, int col, int dim) +{ +#ifdef DRAW_TEXT_GDK + if (dctx->uctx.type == DRAWTYPE_GDK) { + if (dim) { +#if GTK_CHECK_VERSION(2,0,0) + GdkColor color; + color.red = dctx->inst->cols[col].red * 2 / 3; + color.green = dctx->inst->cols[col].green * 2 / 3; + color.blue = dctx->inst->cols[col].blue * 2 / 3; + gdk_gc_set_rgb_fg_color(dctx->uctx.u.gdk.gc, &color); +#else + /* Poor GTK1 fallback */ + gdk_gc_set_foreground(dctx->uctx.u.gdk.gc, &dctx->inst->cols[col]); +#endif + } else { + gdk_gc_set_foreground(dctx->uctx.u.gdk.gc, &dctx->inst->cols[col]); + } + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (dctx->uctx.type == DRAWTYPE_CAIRO) { + cairo_set_source_rgb_dim(dctx->uctx.u.cairo.cr, + dctx->inst->cols[col].red / 65535.0, + dctx->inst->cols[col].green / 65535.0, + dctx->inst->cols[col].blue / 65535.0, dim); + } +#endif +} + +static void draw_set_colour_rgb(struct draw_ctx *dctx, optionalrgb orgb, + int dim) { #ifdef DRAW_TEXT_GDK if (dctx->uctx.type == DRAWTYPE_GDK) { - gdk_gc_set_foreground(dctx->uctx.u.gdk.gc, &dctx->inst->cols[col]); +#if GTK_CHECK_VERSION(2,0,0) + GdkColor color; + color.red = orgb.r * 256; + color.green = orgb.g * 256; + color.blue = orgb.b * 256; + if (dim) { + color.red = color.red * 2 / 3; + color.green = color.green * 2 / 3; + color.blue = color.blue * 2 / 3; + } + gdk_gc_set_rgb_fg_color(dctx->uctx.u.gdk.gc, &color); +#else + /* Poor GTK1 fallback */ + gdk_gc_set_foreground(dctx->uctx.u.gdk.gc, &dctx->inst->cols[256]); +#endif } #endif #ifdef DRAW_TEXT_CAIRO if (dctx->uctx.type == DRAWTYPE_CAIRO) { - cairo_set_source_rgb(dctx->uctx.u.cairo.cr, - dctx->inst->cols[col].red / 65535.0, - dctx->inst->cols[col].green / 65535.0, - dctx->inst->cols[col].blue / 65535.0); + cairo_set_source_rgb_dim(dctx->uctx.u.cairo.cr, orgb.r / 255.0, + orgb.g / 255.0, orgb.b / 255.0, dim); } #endif } @@ -3209,7 +3686,7 @@ static void draw_backing_rect(struct gui_data *inst) struct draw_ctx *dctx = get_ctx(inst); int w = inst->width * inst->font_width + 2*inst->window_border; int h = inst->height * inst->font_height + 2*inst->window_border; - draw_set_colour(dctx, 258); + draw_set_colour(dctx, 258, FALSE); draw_rectangle(dctx, 1, 0, 0, w, h); draw_update(dctx, 0, 0, w, h); free_ctx(dctx); @@ -3222,7 +3699,7 @@ static void draw_backing_rect(struct gui_data *inst) * We are allowed to fiddle with the contents of `text'. */ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) + unsigned long attr, int lattr, truecolour truecolour) { struct draw_ctx *dctx = (struct draw_ctx *)ctx; struct gui_data *inst = dctx->inst; @@ -3237,12 +3714,21 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, } else ncombining = 1; + if (monochrome) + truecolour.fg = truecolour.bg = optionalrgb_none; + nfg = ((monochrome ? ATTR_DEFFG : (attr & ATTR_FGMASK)) >> ATTR_FGSHIFT); nbg = ((monochrome ? ATTR_DEFBG : (attr & ATTR_BGMASK)) >> ATTR_BGSHIFT); if (!!(attr & ATTR_REVERSE) ^ (monochrome && (attr & TATTR_ACTCURS))) { + struct optionalrgb trgb; + t = nfg; nfg = nbg; nbg = t; + + trgb = truecolour.fg; + truecolour.fg = truecolour.bg; + truecolour.bg = trgb; } if ((inst->bold_style & 2) && (attr & ATTR_BOLD)) { if (nfg < 16) nfg |= 8; @@ -3253,8 +3739,10 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, else if (nbg >= 256) nbg |= 1; } if ((attr & TATTR_ACTCURS) && !monochrome) { + truecolour.fg = truecolour.bg = optionalrgb_none; nfg = 260; nbg = 261; + attr &= ~ATTR_DIM; /* don't dim the cursor */ } fontid = shadow = 0; @@ -3316,13 +3804,19 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, ((lattr & LATTR_MODE) == LATTR_BOT)); } - draw_set_colour(dctx, nbg); + if (truecolour.bg.enabled) + draw_set_colour_rgb(dctx, truecolour.bg, attr & ATTR_DIM); + else + draw_set_colour(dctx, nbg, attr & ATTR_DIM); draw_rectangle(dctx, TRUE, x*inst->font_width+inst->window_border, y*inst->font_height+inst->window_border, rlen*widefactor*inst->font_width, inst->font_height); - draw_set_colour(dctx, nfg); + if (truecolour.fg.enabled) + draw_set_colour_rgb(dctx, truecolour.fg, attr & ATTR_DIM); + else + draw_set_colour(dctx, nfg, attr & ATTR_DIM); if (ncombining > 1) { assert(len == 1); unifont_draw_combining(&dctx->uctx, inst->fonts[fontid], @@ -3362,13 +3856,13 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, } void do_text(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) + unsigned long attr, int lattr, truecolour truecolour) { struct draw_ctx *dctx = (struct draw_ctx *)ctx; struct gui_data *inst = dctx->inst; int widefactor; - do_text_internal(ctx, x, y, text, len, attr, lattr); + do_text_internal(ctx, x, y, text, len, attr, lattr, truecolour); if (attr & ATTR_WIDE) { widefactor = 2; @@ -3392,7 +3886,7 @@ void do_text(Context ctx, int x, int y, wchar_t *text, int len, } void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) + unsigned long attr, int lattr, truecolour truecolour) { struct draw_ctx *dctx = (struct draw_ctx *)ctx; struct gui_data *inst = dctx->inst; @@ -3409,7 +3903,7 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, active = 1; } else active = 0; - do_text_internal(ctx, x, y, text, len, attr, lattr); + do_text_internal(ctx, x, y, text, len, attr, lattr, truecolour); if (attr & TATTR_COMBINING) len = 1; @@ -3436,7 +3930,7 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, * if it's passive. */ if (passive) { - draw_set_colour(dctx, 261); + draw_set_colour(dctx, 261, FALSE); draw_rectangle(dctx, FALSE, x*inst->font_width+inst->window_border, y*inst->font_height+inst->window_border, @@ -3475,7 +3969,7 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, length = inst->font_height; } - draw_set_colour(dctx, 261); + draw_set_colour(dctx, 261, FALSE); if (passive) { for (i = 0; i < length; i++) { if (i % 2 == 0) { @@ -3544,17 +4038,6 @@ void modalfatalbox(const char *p, ...) exit(1); } -void cmdline_error(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "%s: ", appname); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); - exit(1); -} - const char *get_x_display(void *frontend) { return gdk_get_display(); @@ -3807,10 +4290,24 @@ void reset_terminal_menuitem(GtkMenuItem *item, gpointer data) ldisc_echoedit_update(inst->ldisc); } +void copy_clipboard_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + static const int clips[] = { MENU_CLIPBOARD }; + term_request_copy(inst->term, clips, lenof(clips)); +} + +void paste_clipboard_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + term_request_paste(inst->term, MENU_CLIPBOARD); +} + void copy_all_menuitem(GtkMenuItem *item, gpointer data) { struct gui_data *inst = (struct gui_data *)data; - term_copyall(inst->term); + static const int clips[] = { COPYALL_CLIPBOARDS }; + term_copyall(inst->term, clips, lenof(clips)); } void special_menuitem(GtkMenuItem *item, gpointer data) @@ -3835,7 +4332,100 @@ void event_log_menuitem(GtkMenuItem *item, gpointer data) showeventlog(inst->eventlogstuff, inst->window); } +void setup_clipboards(struct gui_data *inst, Terminal *term, Conf *conf) +{ + assert(term->mouse_select_clipboards[0] == CLIP_LOCAL); + + term->n_mouse_select_clipboards = 1; + term->mouse_select_clipboards[ + term->n_mouse_select_clipboards++] = MOUSE_SELECT_CLIPBOARD; + + if (conf_get_int(conf, CONF_mouseautocopy)) { + term->mouse_select_clipboards[ + term->n_mouse_select_clipboards++] = CLIP_CLIPBOARD; + } + + set_clipboard_atom(inst, CLIP_CUSTOM_1, GDK_NONE); + set_clipboard_atom(inst, CLIP_CUSTOM_2, GDK_NONE); + set_clipboard_atom(inst, CLIP_CUSTOM_3, GDK_NONE); + + switch (conf_get_int(conf, CONF_mousepaste)) { + case CLIPUI_IMPLICIT: + term->mouse_paste_clipboard = MOUSE_PASTE_CLIPBOARD; + break; + case CLIPUI_EXPLICIT: + term->mouse_paste_clipboard = CLIP_CLIPBOARD; + break; + case CLIPUI_CUSTOM: + term->mouse_paste_clipboard = CLIP_CUSTOM_1; + set_clipboard_atom(inst, CLIP_CUSTOM_1, + gdk_atom_intern( + conf_get_str(conf, CONF_mousepaste_custom), + FALSE)); + break; + default: + term->mouse_paste_clipboard = CLIP_NULL; + break; + } + + if (conf_get_int(conf, CONF_ctrlshiftins) == CLIPUI_CUSTOM) { + GdkAtom atom = gdk_atom_intern( + conf_get_str(conf, CONF_ctrlshiftins_custom), FALSE); + struct clipboard_state *state = clipboard_from_atom(inst, atom); + if (state) { + inst->clipboard_ctrlshiftins = state->clipboard; + } else { + inst->clipboard_ctrlshiftins = CLIP_CUSTOM_2; + set_clipboard_atom(inst, CLIP_CUSTOM_2, atom); + } + } + + if (conf_get_int(conf, CONF_ctrlshiftcv) == CLIPUI_CUSTOM) { + GdkAtom atom = gdk_atom_intern( + conf_get_str(conf, CONF_ctrlshiftcv_custom), FALSE); + struct clipboard_state *state = clipboard_from_atom(inst, atom); + if (state) { + inst->clipboard_ctrlshiftins = state->clipboard; + } else { + inst->clipboard_ctrlshiftcv = CLIP_CUSTOM_3; + set_clipboard_atom(inst, CLIP_CUSTOM_3, atom); + } + } +} + +struct after_change_settings_dialog_ctx { + struct gui_data *inst; + Conf *newconf; +}; + +static void after_change_settings_dialog(void *vctx, int retval); + void change_settings_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + struct after_change_settings_dialog_ctx *ctx; + GtkWidget *dialog; + char *title; + + if (find_and_raise_dialog(inst, DIALOG_SLOT_RECONFIGURE)) + return; + + title = dupcat(appname, " Reconfiguration", NULL); + + ctx = snew(struct after_change_settings_dialog_ctx); + ctx->inst = inst; + ctx->newconf = conf_copy(inst->conf); + + dialog = create_config_box( + title, ctx->newconf, 1, + inst->back ? inst->back->cfg_info(inst->backhandle) : 0, + after_change_settings_dialog, ctx); + register_dialog(inst, DIALOG_SLOT_RECONFIGURE, dialog); + + sfree(title); +} + +static void after_change_settings_dialog(void *vctx, int retval) { /* This maps colour indices in inst->conf to those used in inst->cols. */ static const int ww[] = { @@ -3843,25 +4433,26 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data) 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15 }; - struct gui_data *inst = (struct gui_data *)data; - char *title; - Conf *oldconf, *newconf; + struct after_change_settings_dialog_ctx ctx = + *(struct after_change_settings_dialog_ctx *)vctx; + struct gui_data *inst = ctx.inst; + Conf *oldconf = inst->conf, *newconf = ctx.newconf; int i, j, need_size; - assert(lenof(ww) == NCFGCOLOURS); + sfree(vctx); /* we've copied this already */ - if (inst->reconfiguring) - return; - else - inst->reconfiguring = TRUE; + if (retval < 0) { + /* If the dialog box was aborted without giving a result + * (probably because the whole session window closed), we have + * nothing further to do. */ + return; + } - title = dupcat(appname, " Reconfiguration", NULL); + assert(lenof(ww) == NCFGCOLOURS); - oldconf = inst->conf; - newconf = conf_copy(inst->conf); + unregister_dialog(inst, DIALOG_SLOT_RECONFIGURE); - if (do_config_box(title, newconf, 1, - inst->back?inst->back->cfg_info(inst->backhandle):0)) { + if (retval) { inst->conf = newconf; /* Pass new config data to the logging module */ @@ -3876,6 +4467,7 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data) } /* Pass new config data to the terminal */ term_reconfig(inst->term, inst->conf); + setup_clipboards(inst, inst->term, inst->conf); /* Pass new config data to the back end */ if (inst->back) inst->back->reconfig(inst->backhandle, inst->conf); @@ -3966,10 +4558,10 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data) char *msgboxtext = dupprintf("Could not change fonts in terminal window: %s\n", errmsg); - messagebox(inst->window, "Font setup error", msgboxtext, - string_width("Could not change fonts in terminal window:"), - FALSE, "OK", 'o', +1, 1, - NULL); + create_message_box( + inst->window, "Font setup error", msgboxtext, + string_width("Could not change fonts in terminal window:"), + FALSE, &buttons_ok, trivial_post_dialog_fn, NULL); sfree(msgboxtext); sfree(errmsg); } else { @@ -4016,8 +4608,6 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data) } else { conf_free(newconf); } - sfree(title); - inst->reconfiguring = FALSE; } static void change_font_size(struct gui_data *inst, int increment) @@ -4119,6 +4709,40 @@ void saved_session_freedata(GtkMenuItem *item, gpointer data) sfree(str); } +void app_menu_action(void *frontend, enum MenuAction action) +{ + struct gui_data *inst = (struct gui_data *)frontend; + switch (action) { + case MA_COPY: + copy_clipboard_menuitem(NULL, inst); + break; + case MA_PASTE: + paste_clipboard_menuitem(NULL, inst); + break; + case MA_COPY_ALL: + copy_all_menuitem(NULL, inst); + break; + case MA_DUPLICATE_SESSION: + dup_session_menuitem(NULL, inst); + break; + case MA_RESTART_SESSION: + restart_session_menuitem(NULL, inst); + break; + case MA_CHANGE_SETTINGS: + change_settings_menuitem(NULL, inst); + break; + case MA_CLEAR_SCROLLBACK: + clear_scrollback_menuitem(NULL, inst); + break; + case MA_RESET_TERMINAL: + reset_terminal_menuitem(NULL, inst); + break; + case MA_EVENT_LOG: + event_log_menuitem(NULL, inst); + break; + } +} + static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data) { struct gui_data *inst = (struct gui_data *)data; @@ -4274,11 +4898,11 @@ static void start_backend(struct gui_data *inst) if (error) { char *msg = dupprintf("Unable to open connection to %s:\n%s", - conf_get_str(inst->conf, CONF_host), error); + conf_dest(inst->conf), error); inst->exited = TRUE; - fatal_message_box(inst->window, msg); + connection_fatal(inst, msg); sfree(msg); - exit(0); + return; } s = conf_get_str(inst->conf, CONF_wintitle); @@ -4302,6 +4926,7 @@ static void start_backend(struct gui_data *inst) gtk_widget_set_sensitive(inst->restartitem, FALSE); } +#if GTK_CHECK_VERSION(2,0,0) static void get_monitor_geometry(GtkWidget *widget, GdkRectangle *geometry) { #if GTK_CHECK_VERSION(3,4,0) @@ -4325,16 +4950,22 @@ static void get_monitor_geometry(GtkWidget *widget, GdkRectangle *geometry) geometry->height = gdk_screen_height(); #endif } +#endif -struct gui_data *new_session_window(Conf *conf, const char *geometry_string) +void new_session_window(Conf *conf, const char *geometry_string) { struct gui_data *inst; + prepare_session(conf); + /* * Create an instance structure and initialise to zeroes */ inst = snew(struct gui_data); memset(inst, 0, sizeof(*inst)); +#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 + inst->cdi_headtail.next = inst->cdi_headtail.prev = &inst->cdi_headtail; +#endif inst->alt_keycode = -1; /* this one needs _not_ to be zero */ inst->busy_status = BUSY_NOT; inst->conf = conf; @@ -4372,17 +5003,21 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) inst->area = gtk_drawing_area_new(); gtk_widget_set_name(GTK_WIDGET(inst->area), "drawing-area"); -#if GTK_CHECK_VERSION(2,0,0) - inst->imc = gtk_im_multicontext_new(); -#endif - { char *errmsg = setup_fonts_ucs(inst); if (errmsg) { - fprintf(stderr, "%s: %s\n", appname, errmsg); - exit(1); + window_setup_error(errmsg); + sfree(errmsg); + gtk_widget_destroy(inst->area); + sfree(inst); + return; } } + +#if GTK_CHECK_VERSION(2,0,0) + inst->imc = gtk_im_multicontext_new(); +#endif + inst->window = make_gtk_toplevel_window(inst); gtk_widget_set_name(GTK_WIDGET(inst->window), "top-level"); { @@ -4625,6 +5260,11 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) gtk_widget_hide(inst->specialsitem2); MKMENUITEM("Clear Scrollback", clear_scrollback_menuitem); MKMENUITEM("Reset Terminal", reset_terminal_menuitem); + MKSEP(); + MKMENUITEM("Copy to " CLIPNAME_EXPLICIT_OBJECT, + copy_clipboard_menuitem); + MKMENUITEM("Paste from " CLIPNAME_EXPLICIT_OBJECT, + paste_clipboard_menuitem); MKMENUITEM("Copy All", copy_all_menuitem); MKSEP(); s = dupcat("About ", appname, NULL); @@ -4645,17 +5285,17 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) inst->eventlogstuff = eventlogstuff_new(); inst->term = term_init(inst->conf, &inst->ucsdata, inst); + setup_clipboards(inst, inst->term, inst->conf); inst->logctx = log_init(inst, inst->conf); term_provide_logctx(inst->term, inst->logctx); term_size(inst->term, inst->height, inst->width, conf_get_int(inst->conf, CONF_savelines)); - start_backend(inst); - - ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */ - inst->exited = FALSE; - return inst; + start_backend(inst); + + if (inst->ldisc) /* early backend failure might make this NULL already */ + ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */ } diff --git a/unix/osxlaunch.c b/unix/osxlaunch.c index 2627c642e..d560df938 100644 --- a/unix/osxlaunch.c +++ b/unix/osxlaunch.c @@ -10,8 +10,8 @@ * * But the GTK program won't start up unless all those shared * libraries etc are already pointed to by environment variables like - * DYLD_LIBRARY_PATH, which won't be set up when the bundle is - * launched. + * GTK_PATH and PANGO_LIBDIR and things like that, which won't be set + * up when the bundle is launched. * * Hence, gtk-mac-bundler expects to install the program in the bundle * under a name like 'Contents/MacOS/Program-bin'; and the file called @@ -48,7 +48,7 @@ #include #include -#ifndef __APPLE__ +#if !defined __APPLE__ && !defined TEST_COMPILE_ON_LINUX /* When we're not compiling for OS X, it's easier to just turn this * program into a trivial hello-world by ifdef in the source than it * is to remove it in the makefile edifice. */ @@ -61,7 +61,24 @@ int main(int argc, char **argv) #include #include + +#ifdef __APPLE__ #include +#else +/* For Linux, a bodge to let as much of this code still run as + * possible, so that you can run it under friendly debugging tools + * like valgrind. */ +int _NSGetExecutablePath(char *out, uint32_t *outlen) +{ + static const char toret[] = "/proc/self/exe"; + if (out != NULL && *outlen < sizeof(toret)) + return -1; + *outlen = sizeof(toret); + if (out) + memcpy(out, toret, sizeof(toret)); + return 0; +} +#endif /* ---------------------------------------------------------------------- * Find an alphabetic prefix unused by any environment variable name. @@ -130,11 +147,13 @@ char *get_unused_env_prefix(void) char **e; qhead = (struct bucket *)malloc(sizeof(struct bucket)); - qhead->prefixlen = 0; if (!qhead) { fprintf(stderr, "out of memory\n"); exit(1); } + qhead->prefixlen = 0; + qhead->first_node = NULL; + qhead->next_bucket = NULL; for (e = environ; *e; e++) qhead->first_node = new_node(qhead->first_node, *e, strcspn(*e, "=")); @@ -151,6 +170,7 @@ char *get_unused_env_prefix(void) exit(1); } buckets[i]->prefixlen = qhead->prefixlen + 1; + buckets[i]->first_node = NULL; qtail->next_bucket = buckets[i]; qtail = buckets[i]; } @@ -335,19 +355,35 @@ char *alloc_cat(const char *str1, const char *str2) * Overwrite an environment variable, preserving the old one for the * real app to restore. */ +void setenv_wrap(const char *name, const char *value) +{ +#ifdef DEBUG_OSXLAUNCH + printf("setenv(\"%s\",\"%s\")\n", name, value); +#endif + setenv(name, value, 1); +} + +void unsetenv_wrap(const char *name) +{ +#ifdef DEBUG_OSXLAUNCH + printf("unsetenv(\"%s\")\n", name); +#endif + unsetenv(name); +} + char *prefix, *prefixset, *prefixunset; void overwrite_env(const char *name, const char *value) { const char *oldvalue = getenv(name); if (oldvalue) { - setenv(alloc_cat(prefixset, name), oldvalue, 1); + setenv_wrap(alloc_cat(prefixset, name), oldvalue); } else { - setenv(alloc_cat(prefixunset, name), "", 1); + setenv_wrap(alloc_cat(prefixunset, name), ""); } if (value) - setenv(name, value, 1); + setenv_wrap(name, value); else - unsetenv(name); + unsetenv_wrap(name); } /* ---------------------------------------------------------------------- @@ -360,6 +396,11 @@ int main(int argc, char **argv) prefixset = alloc_cat(prefix, "s"); prefixunset = alloc_cat(prefix, "u"); +#ifdef DEBUG_OSXLAUNCH + printf("Environment prefixes: main=\"%s\", set=\"%s\", unset=\"%s\"\n", + prefix, prefixset, prefixunset); +#endif + char *prog_path = get_program_path(); // /Contents/MacOS/ char *macos = dirname_wrapper(prog_path); // /Contents/MacOS char *contents = dirname_wrapper(macos); // /Contents @@ -374,7 +415,7 @@ int main(int argc, char **argv) char *locale = alloc_cat(share, "/locale"); char *realbin = alloc_cat(prog_path, "-bin"); - overwrite_env("DYLD_LIBRARY_PATH", lib); +// overwrite_env("DYLD_LIBRARY_PATH", lib); overwrite_env("XDG_CONFIG_DIRS", xdg); overwrite_env("XDG_DATA_DIRS", share); overwrite_env("GTK_DATA_PREFIX", resources); @@ -395,19 +436,35 @@ int main(int argc, char **argv) } int j = 0; new_argv[j++] = realbin; +#ifdef DEBUG_OSXLAUNCH + printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]); +#endif { int i = 1; if (i < argc && !strncmp(argv[i], "-psn_", 5)) i++; - for (; i < argc; i++) + for (; i < argc; i++) { new_argv[j++] = argv[i]; +#ifdef DEBUG_OSXLAUNCH + printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]); +#endif + } } new_argv[j++] = prefix; +#ifdef DEBUG_OSXLAUNCH + printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]); +#endif new_argv[j++] = NULL; +#ifdef DEBUG_OSXLAUNCH + printf("executing \"%s\"\n", realbin); +#endif execv(realbin, new_argv); perror("execv"); + free(new_argv); + free(contents); + free(macos); return 127; } diff --git a/unix/pterm.bundle b/unix/pterm.bundle index 377fee0dc..0d7012160 100644 --- a/unix/pterm.bundle +++ b/unix/pterm.bundle @@ -2,7 +2,11 @@ - ${env:JHBUILD_PREFIX} + + ${env:PUTTY_GTK_PREFIX_FROM_MAKEFILE} + gtk+-3.0 + ${env:PUTTY_GTK_PREFIX_FROM_MAKEFILE} + gtk+-3.0