summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Golubev <fatzer2@gmail.com>2025-11-01 17:17:59 +0300
committerFat-Zer <fatzer2@gmail.com>2025-11-25 16:46:18 +0000
commit2f927acd565e0741e8c299c89152e1cf92ecac43 (patch)
treeb8826e1ff3b21a91a25fb329e8c0c1d31feadf99
parent7583327200ad3f29dbf447a21e2a90600c7ed292 (diff)
downloadtdegraphics-2f927acd565e0741e8c299c89152e1cf92ecac43.tar.gz
tdegraphics-2f927acd565e0741e8c299c89152e1cf92ecac43.zip
ksnapshot: declare text/uri-list format when drag&droppingHEADmaster
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>
-rw-r--r--ksnapshot/ksnapshot.cpp245
-rw-r--r--ksnapshot/ksnapshot.h29
2 files changed, 204 insertions, 70 deletions
diff --git a/ksnapshot/ksnapshot.cpp b/ksnapshot/ksnapshot.cpp
index 605a898a..36918c4a 100644
--- a/ksnapshot/ksnapshot.cpp
+++ b/ksnapshot/ksnapshot.cpp
@@ -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"
diff --git a/ksnapshot/ksnapshot.h b/ksnapshot/ksnapshot.h
index 47cbe47e..45734f88 100644
--- a/ksnapshot/ksnapshot.h
+++ b/ksnapshot/ksnapshot.h
@@ -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