ksnapshot: declare text/uri-list format when drag&dropping

This enables drag&dropping screenshots directly into a browser window.
As a side effect when drag&dropping into most file managers (into
konquerror in particular) this will also cause them to copy a png file
instead of asking a user in what format they would like to save the
image.

Besides that commit includes:
- Rework of handling of temporary files, in particular:
  - Temporary files now try to use a friendlier names with timestamps
    e.g. `snapshot-20251108-211852.png`. And fallback to use
    `mkstemp()`-style suffix if the name already in use.
  - When "Open with" is issued several times the temporary file will be
    created only once.
  - If the file was already saved by the user ksnapshot won't create an
    additional temporary one for drag&drop or "Open with", but rather
    use the saved one.
  - "Open with" temporary files are no longer removed after the process
    terminates, but are kept around until exit from ksnapshot. Removing
    the files right after process termination caused failures to open
    the file when they were passed to another (already running) instance
    of a program instead of a new one (e.g. in gimp).
- Some refactoring for code deduplocation.
- Some helper classes.

Closes: https://mirror.git.trinitydesktop.org/gitea/TDE/tdegraphics/issues/135
Signed-off-by: Alexander Golubev <fatzer2@gmail.com>
pull/137/head
Alexander Golubev 2 months ago
parent 4ba7dac648
commit fec1f3dabb

@ -22,10 +22,11 @@
#include <kprinter.h>
#include <tdeio/netaccess.h>
#include <ksavefile.h>
#include <tdestandarddirs.h>
#include <tdetempfile.h>
#include <kde_file.h>
#include <tqbitmap.h>
#include <tqdragobject.h>
#include <tqimage.h>
#include <tqclipboard.h>
#include <tqvbox.h>
@ -46,8 +47,15 @@
#include <tqpaintdevicemetrics.h>
#include <tqwhatsthis.h>
#include <functional>
#include <memory>
#include <utility>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include "ksnapshot.h"
#include "regiongrabber.h"
#include "windowgrabber.h"
@ -60,6 +68,26 @@
#include <tdeglobal.h>
// A quick and dirty scope guard implementation
class ExitGuard {
public:
template<class Callable>
ExitGuard(Callable && undo_func) : f(std::forward<Callable>(undo_func)) {}
ExitGuard(ExitGuard && other) : f(std::move(other.f)) {
other.f = nullptr;
}
~ExitGuard() {
if(f) { f(); f = nullptr; }
}
ExitGuard(const ExitGuard&) = delete;
void operator= (const ExitGuard&) = delete;
private:
std::function<void()> f;
};
KSnapshot::KSnapshot(TQWidget *parent, const char *name, bool grabCurrent)
: DCOPObject("interface"),
KDialogBase(parent, name, true, TQString(), Help|User1, User1,
@ -85,9 +113,11 @@ KSnapshot::KSnapshot(TQWidget *parent, const char *name, bool grabCurrent)
grabber->show();
grabber->grabMouse( waitCursor );
if ( !grabCurrent )
if ( !grabCurrent ) {
snapshot = TQPixmap::grabWindow( tqt_xrootwin() );
else {
timestamp = TQDateTime::currentDateTime();
setLocalFilePath(TQString::null);
} else {
mainWidget->setMode( WindowUnderCursor );
mainWidget->setIncludeDecorations( true );
performGrab();
@ -201,8 +231,12 @@ bool KSnapshot::save( const KURL& url )
if ( url.isLocalFile() ) {
KSaveFile saveFile( url.path() );
if ( saveFile.status() == 0 ) {
if ( snapshot.save( saveFile.file(), type.latin1() ) )
if ( snapshot.save( saveFile.file(), type.latin1() ) ) {
ok = saveFile.close();
if (ok) {
setLocalFilePath(saveFile.name());
}
}
}
}
else {
@ -270,7 +304,7 @@ void KSnapshot::slotCopy()
void KSnapshot::slotDragSnapshot()
{
TQDragObject *drobj = new TQImageDrag(snapshot.convertToImage(), this);
TQDragObject *drobj = new SnapshotDrag(snapshot.convertToImage(), this);
drobj->setPixmap(mainWidget->preview());
drobj->dragCopy();
}
@ -356,26 +390,18 @@ void KSnapshot::slotPrint()
void KSnapshot::slotRegionGrabbed( const TQPixmap &pix )
{
if ( !pix.isNull() )
{
snapshot = pix;
updatePreview();
modified = true;
updateCaption();
}
delete rgnGrab;
TQApplication::restoreOverrideCursor();
move(oldWinPos);
show();
rgnGrab->deleteLater();
newSnapshot(pix);
}
void KSnapshot::slotWindowGrabbed( const TQPixmap &pix )
void KSnapshot::newSnapshot( const TQPixmap &pix )
{
if ( !pix.isNull() )
{
snapshot = pix;
updatePreview();
timestamp = TQDateTime::currentDateTime();
setLocalFilePath(TQString::null);
modified = true;
updateCaption();
}
@ -397,68 +423,117 @@ void KSnapshot::slotOpenWithKP() {
}
}
void KSnapshot::openWithExternalApp(const KService &service) {
// Write snapshot to temporary file
bool ok = false;
KTempFile *tmpFile = new KTempFile;
if (tmpFile->status() == 0) {
if (snapshot.save(tmpFile->file(), "PNG")) {
if (tmpFile->close()) {
ok = true;
}
/// Writes the snapshot to a temporary file
TQString KSnapshot::saveTempFile() {
// construct a pretty name for the temporary file
TQString base_fname =
i18n("A temporary filename; prefer dashes (-) as word separators if required", "snapshot")
.append(timestamp.toString("-yyyyMMdd-hhmmss"));
TQString base_fpath = locateLocal("tmp", base_fname);
TQString fname = base_fpath + ".png";
// We want the pretty names; unfortunately KTempFile forces us to use mkstemp-style name; which
// has a random string at the end, which is quite ugly. On the other hand TQFile doesn't
// provide any means to fail if file already exist (i.e. O_EXCL). So in order to have best of
// both worlds we will have to try to open the file manually and if it fails, we will fallback
// to KTempFile.
int fd = -1;
ExitGuard closeGuard { [&fd](){ if (fd>=0) { ::close(fd); } } };
std::unique_ptr<KTempFile> tmpFile; // used only if manual open() fails
std::unique_ptr<TQFile> tqfile; // used only if manual open() succeeds
TQFile* file; // either tqfile or tmpFile->file()
fd = KDE_open(TQFile::encodeName(fname), O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd>=0) {
tqfile = std::unique_ptr<TQFile>(new TQFile(fname));
tqfile->open( IO_WriteOnly, fd ); // according to docs TQFile won't close the fd so there is
// an exit guard above
file = tqfile.get();
} else {
tmpFile = std::unique_ptr<KTempFile>(new KTempFile(base_fpath + "-", ".png"));
if (!tmpFile || tmpFile->status() != 0) {
return TQString::null;
}
file = tmpFile->file();
fname = tmpFile->name();
}
// Actually save the image
bool ok = snapshot.save(file, "PNG");
if (!ok) {
file->remove();
return TQString::null;
}
tempFiles.append(fname);
return fname;
}
void KSnapshot::setLocalFilePath(TQString fp) {
localFilePath = fp;
if(!fp.isEmpty()) {
TQFileInfo fi(fp);
currentFilePathTimestamp = fi.lastModified ();
} else {
currentFilePathTimestamp = TQDateTime();
}
}
/// Returns a local file for the current snapshot. It may be either a file explicitly saved by the
/// user or a temporary one saved internally
TQString KSnapshot::localFile() {
bool needSaveTemp = true;
if(!localFilePath.isEmpty()) {
TQFileInfo fi(localFilePath);
// Checks that nobody have overwritten the file since we saved it
if( fi.exists() && currentFilePathTimestamp == fi.lastModified()) {
needSaveTemp = false;
}
}
if(needSaveTemp) {
setLocalFilePath( saveTempFile() );
}
return localFilePath;
}
void KSnapshot::openWithExternalApp(const KService &service) {
// Write snapshot to temporary file
TQString file = localFile();
if (file.isEmpty()) {
KMessageBox::error(this, i18n("KSnapshot was unable to create temporary file."),
i18n("Unable to save image"));
delete tmpFile;
return;
}
// Launch application
KURL::List list;
list.append(tmpFile->name());
list.append(file);
TQStringList args = KRun::processDesktopExec(service, list, false, false);
TDEProcess *externalApp = new TDEProcess;
*externalApp << args;
connect(externalApp, TQ_SIGNAL(processExited(TDEProcess*)),
this, TQ_SLOT(slotExternalAppClosed(TDEProcess*)));
if (!externalApp->start(TDEProcess::OwnGroup)) {
KMessageBox::error(this, i18n("Cannot start %1!").arg(service.name()));
delete tmpFile;
return;
}
m_tmpFiles[externalApp] = tmpFile;
}
void KSnapshot::slotExternalAppClosed(TDEProcess *process)
{
if (process && m_tmpFiles.contains(process))
{
KTempFile *tmpFile = m_tmpFiles[process];
if (tmpFile)
{
snapshot.load(tmpFile->name());
updatePreview();
tmpFile->unlink();
delete tmpFile;
}
m_tmpFiles.remove(process);
}
}
void KSnapshot::slotAboutToQuit()
{
for (KTempFile *tmpFile : m_tmpFiles)
for(const TQString &file: tempFiles)
{
tmpFile->unlink();
delete tmpFile;
TQFile::remove(file);
}
m_tmpFiles.clear();
tempFiles.clear();
TDEConfig *conf=TDEGlobal::config();
conf->setGroup("GENERAL");
@ -550,21 +625,20 @@ void KSnapshot::performGrab()
if ( mainWidget->mode() == ChildWindow ) {
WindowGrabber wndGrab;
connect( &wndGrab, TQ_SIGNAL( windowGrabbed( const TQPixmap & ) ),
TQ_SLOT( slotWindowGrabbed( const TQPixmap & ) ) );
TQ_SLOT( newSnapshot( const TQPixmap & ) ) );
wndGrab.exec();
}
else if ( mainWidget->mode() == WindowUnderCursor ) {
snapshot = WindowGrabber::grabCurrent( mainWidget->includeDecorations() );
}
else {
snapshot = TQPixmap::grabWindow( tqt_xrootwin() );
TQPixmap pix;
if ( mainWidget->mode() == WindowUnderCursor ) {
pix = WindowGrabber::grabCurrent( mainWidget->includeDecorations() );
}
else {
pix = TQPixmap::grabWindow( tqt_xrootwin() );
}
newSnapshot(pix);
}
updatePreview();
TQApplication::restoreOverrideCursor();
modified = true;
updateCaption();
move(oldWinPos);
show();
}
void KSnapshot::setTime(int newTime)
@ -589,6 +663,8 @@ void KSnapshot::setURL( const TQString &url )
void KSnapshot::setPixmap(const TQPixmap &newImage) {
snapshot = newImage;
timestamp = TQDateTime::currentDateTime();
setLocalFilePath(TQString::null);
updatePreview();
}
@ -616,4 +692,41 @@ void KSnapshot::exit()
{
reject();
}
SnapshotDrag::SnapshotDrag( TQImage image, KSnapshot * dragSource, const char * name )
:TQImageDrag::TQImageDrag( image, dragSource, name ), ksnap(dragSource)
{
// TQ*Drop API is a bit quirky, so to append our value to the list of formats we will have
// to iterate over the full list provided by TQImageDrag manually just to determine how many
// formats it supports
for (int i=0 ; ; i++) {
const char* format = TQImageDrag::format(i);
if (format) {
formats.append(format);
} else {
break;
}
}
formats.append("text/uri-list");
}
const char * SnapshotDrag::format(int i) const
{
if( i < (int) formats.count() ) {
return formats[i];
} else {
return 0;
}
}
TQByteArray SnapshotDrag::encodedData(const char* format) const
{
if( strcmp(format, "text/uri-list") == 0 ) {
return TQUriDrag::localFileToUri( ksnap->localFile() );
} else {
return TQImageDrag::encodedData(format);
}
}
#include "ksnapshot.moc"

@ -4,8 +4,9 @@
#include <tqbitmap.h>
#include <tqcursor.h>
#include <tqdatetime.h>
#include <tqdragobject.h>
#include <tqlabel.h>
#include <tqmap.h>
#include <tqpainter.h>
#include <tqpixmap.h>
#include <tqstyle.h>
@ -102,6 +103,7 @@ public:
bool save( const TQString &filename );
TQString url() const { return filename.url(); }
TQString localFile();
protected slots:
void slotAboutToQuit();
@ -112,7 +114,6 @@ protected slots:
void slotPrint();
void slotOpenWith(int id);
void slotOpenWithKP();
void slotExternalAppClosed(TDEProcess *process);
void slotMovePointer( int x, int y );
void setTime(int newTime);
@ -134,7 +135,7 @@ private slots:
void updateCaption();
void updatePreview();
void slotRegionGrabbed( const TQPixmap & );
void slotWindowGrabbed( const TQPixmap & );
void newSnapshot( const TQPixmap & );
private:
bool save( const KURL& url );
@ -143,6 +144,8 @@ private:
void autoincFilename();
int grabMode();
int timeout();
TQString saveTempFile();
void setLocalFilePath(TQString fp);
TQPixmap snapshot;
TQTimer grabTimer;
@ -151,11 +154,29 @@ private:
KURL filename;
KSnapshotWidget *mainWidget;
RegionGrabber *rgnGrab;
TQDateTime timestamp;
bool modified;
TDETrader::OfferList openWithOffers;
TQMap<TDEProcess*, KTempFile*> m_tmpFiles;
TQString localFilePath;
TQDateTime currentFilePathTimestamp;
TQValueList<TQString> tempFiles;
TQPoint oldWinPos;
};
/**
* A small wrapper around TQImageDrag that also passes text/uri-list with a
* temporary file as a fallback variant.
*/
class SnapshotDrag : public TQImageDrag {
TQ_OBJECT
KSnapshot *ksnap;
TQValueList<TQCString> formats;
public:
SnapshotDrag( TQImage image, KSnapshot * dragSource, const char * name = 0 );
const char * format(int i) const;
TQByteArray encodedData(const char* format) const;
};
#endif // KSNAPSHOT_H

Loading…
Cancel
Save