Remove orig tar stuff.

ubuntu/cosmic
Simon Quigley 6 years ago
parent 30fcfd50cc
commit 52954c35b8

@ -1,6 +0,0 @@
Upstream Authors:
LXQt team: https://lxqt.org
Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
Copyright:
Copyright (c) 2013-2018 LXQt team

@ -1,678 +0,0 @@
libfm-qt-0.13.1 / 2018-06-02
============================
* Bump patch version to 1
* It's supposedly fixed in Qt-5.11.1
* Disconnect old source model
0.13.0 / 2018-05-21
===================
* Release 0.13.0: Update changelog
* Bumped minor version to 13
* Fixed shortcut detection
* Delete CachedFolderModel immediately on unreffing it
* Fix crashes in new templates code
* Remove the C++ wrapper for the old FmPath struct in libfm.
* Avoid using FmPath from libfm in FmSearch.
* Removed redundant code
* Modify Fm::BasicFileLauncher APIs to use Fm::FileInfoPtr consistently.
* Add typedef FileInfoPtr for std::shared_ptr<const FileInfo> since it's so frequently used.
* Correctly handle the mounting of mountable paths and correctly parse the target URIs of shortcuts.
* Add new APIs Fm::MountOperation::mountEnclosingVolume() and Fm::MountOperation::mountMountable() and deprecate Fm::MountOperation::mount().
* Also cover local shortcuts
* Added "reject" to exec dialog
* Delete some libfm compatibility wrappers.
* Delete the unused old FmTemplate wrapper.
* Create the template items in the "Create New" menu with the new C++ file template APIs.
* Port template support to C++ (Fm::Templates class).
* Add convinient functions in Fm::FileOperation to set dest paths for file transfer jobs.
* Made job async again
* Fixed launching of desktop files
* Cleanup
* CMake: Prevent in-source builds
* Fix Fm::FileInfo::isDir() and isExecutableType() to make its behavior consistent with libfm.
* Port file lancher to C++ and Implement Fm::BasicFileLauncher base class. * Migrate Fm::FileLauncher to the new C++ implementation.
* Port archiver integration to C++ (#182)
* Fully port all file operations to C++ 11 (#181)
* Add kitty to terminals.list
* Italic font for hidden items
* Select multiple files
* No visible cursor in File Properties labels
* Just corrected a misspelling
* Elided labels for file operation dialog
* fix namespace, fixes lxqt/libfm-qt/issues/174
* Misc link fixes
* Fixes some pathes after repo move
* Fix a cause of crash in `AppChooserComboBox`
* build: Bump version
* Drop Fm::IconTheme
* iconinfo: Properly handle multiple names
* Some code cleanup
* Fixed custom action execution mode
* Don't drop on files
* Prevent possible c++11 range-loop container detach
* See `.bak` and `.old` as backup extensions; also follow GLib
* Ensure that rename editor has opaque background
* Use special/custom folder icons for bookmarks
* Added two missing cases of `mapFromSource()`
* Support hiding items in Places side-pane
* Fixed sorting by type/owner
* Fix lambda connections in filedialog (#159)
* Drop Q_FOREACH
* Fix memory leak in thumbnail loading (#150)
* Be more tolerant
* Fix the "Folders" key in custom actions
* Add Group column and don't use owner's full name
* Fix comparison of integers of different signs
* Guarantee 64-bit time attributes (#148)
* Added a proxy setting for backup as hidden (#145)
* Fixed the logic of queued deletion
* Track folders containing cut files only with QString
* Copy selected pathbar text to selection clipboard
* Smooth scrolling for icon and thumbnail views
* cmake: Handle CMP0071
* Prepare libfm-qt for bulk rename
* No change queue of files in the deletion queue
* FileInfo: Fix potential SEGFAULT
* Fix two devices for one mount
* Always wait for the folder to load before selecting
* Really cancel multiple renaming on cancelling
* Prevent a potential crash in xdndworkaround
* Fix wrong gray-out of cut files
* Merge side-pane with its surroundings
* Prompt dialog specifically for desktop files
* Remove unnecessary noise
0.12.0 / 2017-10-21
===================
* Release 0.12.0: Update changelog
* Add data transferred to file operation dialog.
* Bump versions
* Disable context-menu actions that cannot be used
* Don't export github templates
* Fix partially visible toggled path buttons
* Add functions to get and set search settings
* Fix mistakes in listview column width calculation
* Add archiver separator only when needed
* Add a separator before archiver actions
* Enable XDS subfolder drop
* UI improvements for Fm::MountOperationPasswordDialog()
* Respect inactiveness when drawing text
* Grey out files that have been Ctrl-X'ed (#88)
* Ignore button for error dialog
* Inline renaming for detailed list view (#110)
* Remove redundant code.
* Prefer local paths if they exist
* Removed QFileInfo (as @PCMan recommended)
* Simplification, optimization and a fix
* Really focus text entry on showing dialog
* Two small fixes
* Keep selection on reloading (if not CPU-intensive)
* Added back/forward buttons and fixed 3 issues
* Reload button, hidden shortcut and a fix
* Implement FileDialog::selectMimeTypeFilter() and QString FileDialog::selectedMimeTypeFilter().
* Initialize folder_ to null
* Fixed the quote issue
* Always preserve explicitly set labels
* Update OK button text and state when needed
* Initialize FileInfo::isShortcut_ (#113)
* Set the selected index current
* Fixd open/save and overwrite prompt
* Set open/save text
* Several important fixes
* Added a missing change
* Preliminary fixes
* Hide UI implementation details for Fm::FileDialog.
* Revert the backward incompatible changes in the constructor of Fm::FolderView.
* Fix a bug in creating new folder for Fm::FileDialog.
* Implement toolbar and quick view mode switches for the Fm::FileDialog class.
* Correctly check file types and test the existence of the selected files as needed.
* Correctly handle item activation.
* Correctly handle filename selection for Fm::FileDialog.
* Correctly handle selected files.
* Implement filename filtering for Fm::FileDialog.
* Check nullity of FileInfo before calling FolderMenu
* Arrange Custom Actions
* Support custom folder icons
* Fix multiple pasting of the same cut file(s)
* Fix KDE clipboard tag for cut file(s)
* Add a basic skeleton for Fm::FileDialog.
* Check nullity of QMimeData (#109)
* MountOperationQuestionDialog: Fix handling responses
* Fix all height issues in horizontal layouts (#103)
* Removed a redundant variable (left there from old tests)
* Fix major bugs in Directory Tree
* Consider desktop text color, now that everything is done here
* Inline Renaming
* Fix compact view regression (#102)
* Fix detailed list crash on double clicking folders
* Removed my garbage
* Fixed issues about spacings and click region
* Make Fm::FolderItemDelegate support painting text shadows and margins so it can completely replace PCManFM::DesktopItemDelegate.
* Avoid using grid size on QListView since this disables any spacing settings.
* liblxqt make no sense for libfm-qt
* Copied issue template
* Add noexcept to move constructors and operator=() so they can be used with STL containers.
* FolderView: Optimize selectAll() (#97)
* Emit fileSizeChanged when needed
* Drops Qt5Core_VERSION_STRING (#96)
* Update size column info (#90)
* Fix Detailed List view DND (#94)
* folderview: Don't allow D&D by Back or Forward
* More fixes (#87)
* Added a missing change signal (#86)
* Fix single items when seaching (#85)
* Check for nullity of IconInfo (#84)
* Address compiler warnings
* Remove addRoots() return type
* Remove the unused data/translations/ entry
* Fix broken folder unmount message caused by incorrect FilePath & GFile* comparison. (#80)
* Remove some superfluous semicolons that lead to pedantic warnings (#79)
* Ensure one item per file (#77)
* Fix the broken filesystem status (free disk space) display. (#78)
* Don't make items current on mouseover (#72)
* Fixes a FTBFS in superbuild mode (#75)
* Replace start tilde in PathEdit (#73)
* Really cancel pending thumbnail jobs on chdir (#74)
* Move fixes (#70)
* Fix invalid pointers (#69)
* Continue file selection on next/previous row (#76)
* Code reformat: use 4-space indentation to match the coding style of other LXQt components.
* Make all constructors explicit so we don't get unwanted object construction by C++.
* Prevent a crash since GObjectPtr's move ctor frees resources
* GObjectPtr: Detect & handle "self-assignment"
* Fix compatibility with Qt 5.6.
* No thumbnail for thumbnails
* Fix thumbnail update
* Fixed `PathBar::setPath()`
* Use real name for renaming
* Prevent a crash plus fallback icon
* Fix custom actions
* volumemanager: Return IconInfo as shared_ptr
* FolderModelItem: Check IconInfo existence
* Bookmarks: Check validity of insert position
* Fix a potential crash of bookmark items when the format of the bookmark file is not correct.
* Only load desktop entry files for native filesystems.
* Fix the missing icon and display name handling of desktop entry files in Fm::FileInfo.
* IconEngine: Use weak_ptr for the parent IconInfo
* PathBar: Avoid leak upon QLayout::replaceWidget()
* Use const iterators
* Use the new lxqt-build-tools new FindExif module
* Fix the incorrect header inclusion in fileoperation.cpp.
* Fix incorrect #include of libfmqtglobals.h.
* Fix a bug when copying to and pasting from "x-special/gnome-copied-files" mime-type.
* Fix bugs in the Custom Actions C++ port.
* Try to port libfm custom actions to C++.
* Try to update the content of a folder after its mount status is changed. Handle cancelled dir listing job properly.
* Rename namespace Fm2 to Fm.
* Remove unused header files of the old C API wrappers.
* Fix bugs in search:// support and finish porting file searching to C++. Fix several bugs in Fm2::FileInfo which caused by null parent dir.
* Add a missing test case for places view.
* Try to add support for menu:// and search:// URI scheme implemented in libfm.
* Correctly destroy Fm2::Job objects when their worker threads terminate.
* Fix incorrect handling of PathBar button creation which causes infinite loop when the underlying GFile implementation contains bugs.
* Fix incorrect path of application menu URI.
* Fix QThread related bugs in PathEdit which causes memory leaks.
* Fix a bug in DirTreeModelItem causing crashes. Also speed up batch insertion of large amount of items.
* Use const iterators (#61)
* Fix the broken folder reload().
* Make all Fm2::Job derived classes reimplement exec() instead of run() method. The new run() method will emit finished() signal automatically when exec() returns.
* Fix memory leaks caused by incorrect use of QThread.
* Fix a memory leak in Fm::ThumbnailJob.
* Fix memory leaks caused by broken cache.
* Fix wrong size of generated thumbnails by external thumbnailers.
* Fix memory bugs in Fm2::GErrorPtr and improve the error handling of Fm2::Job and Fm2::Folder.
* Fix some errors related to incorrect use of std::remove() in Fm2::Folder. Replace QList with std::vector and use binary search when inserting items for the Fm::DirTreeModelItem.
* Change the handling of Fm::FolderView::selChanged signal to make it more efficient.
* Port to the new Fm2::TotalSizeJob API.
* Fix compatibility with libfm custom actions.
* Add some compatibility API which helps migrating old C APIs to the new C++ implementation.
* Convert datetime to locale-aware strings using QDateTime.
* Use QCollator to perform file sorting.
* Fix detailed view.
* Finish porting DirTreeModel to the new API. Fix bugs in Fm2::FilePath and Fm2::FileInfo APIs.
* Port the libfm terminal emulator related APIs to C++.
* Rename some methods in Fm2::Folder and Fm2::FileInfo for consistency.
* Port to the new IconInfo API and deprecate manual icon update when the theme is changed.
* Rename Fm::Icon to Fm::IconInfo.
* Port emblem support to the new libfm C++ API.
* Remove unused files, including some old APIs. Replace QVector in BrowseHistory with STL vector.
* Fix a bug in Fm::FileMenu.
* Port file-click handling to the new C++ API.
* Fix bugs in Fm::PathBar getting wrong path when a path button is toggled.
* Remove Fm::FilePath(const char* path_str) to avoid ambiguity.
* Replace all NULL with C++ 11 nullptr;
* Fix FilePath related errors caused by incomplete porting.
* Make Fm::FolderConfig support the new Fm::FilePath class.
* Fix Fm::BookmarkAction to use the new C++ API.
* Fix missing icons of places view caused by memory errors.
* Fix memory errors in Fm2::Bookmarks::reorder(). Add a small test case for the places view.
* Share the same places model among all views.
* Port most of the existing UI-related APIs to the new C++ APIs (excluding the file operations).
* Port the path bar to the new Fm2 API.
* Implement VolumeManager class which is a QObject wrapper of gio GVolumeMonitor.
* Add some getters for Volume and Mount classes.
* Properly associate external thumbnailers with mimetypes they support and fix thumbnail generation from thumbnailers.
* Start porting thumbnail loaders to the new C++ APIs. Add new Fm::ThumbnailJob used to load thumbnails for a given file list. Add commonDirPath paramter to Fm::FileInfoJob to reduce memory usage.
* Add the missing test case for folder view.
* Start porting Fm::FolderModel and Fm::FolderView to the new libfm core c++ API.
* Work in progress.
* Add a c++ wrapper for GFileMonitor. Add LIBFM_QT_API declaration for all public headers.
* Port error handling mechanism of FmJob to C++ and improve the GError wrapper class.
* Bump year
* Add gioptrs.h which defines smart pointer types wrapping gio related data types. Add some basic skeleton for various I/O jobs classes.
* Start porting Copyjob and add basic skeleton for untrash and symlink jobs.
* Finish porting FmFolder to C++.
* Add a very simple test case to test the new Fm core C++ code. Fix bugs in smart pointers and do empty base class optimization for CStrPtr.
* Improve Fm::Folder.
* Rename UserInfo to UserInfoCache.
* Port Fm::Bookmarks to C++.
* Port FmDeepCountJob to C++.
* Add basic skeletion to Fm::VolumeManager.
* Implement Fm2::UserInfo, which is a cache for uid/gid name mapping.
* Add basic skeleton for other C++ classes waiting for porting.
* Add GSignalHandler to do automatic signal/slot connection management with type safety for GObject.
* Add basic skeleton for the C++ 11 port of FmFileInfoJob.
* Try to port Fm::Folder and Fm::DirListJob to Qt5 and C++ 11.
* Try to port FmIcon, FmFileInfo, and FmMimeType of libfm to clean C++.
* Add smart pointer for GObject-derived classes and add Fm::FilePath class which wraps GFile.
0.11.2 / 2016-12-21
===================
* Release 0.11.2: Update changelog
* Fix enabled state of path arrows on starting (#58)
* bump patch version (#56)
* Use QByteArray::constData() where we can (#57)
* Updates lxqt-build-tools required version
* Bump ABI so version numbers preparing for a new release.
* Fix Pathbar Paint on Menu Pop-Up
* Code cleanup and refactor for Fm::PathBar.
* Added another condition
* Added a missing condition (typo)
* Scroll Pathbar with Mouse Wheel
* Reduct flickering of the path bar when creating path buttons.
* Code simplification by removing unnecessary signal/slot usage.
* Path Button Middle Click
* Enable auto-repeat for pathbar scroll buttons.
* Make the path bar buttons aware of style changes.
* Use widget style instead of app style
* Align Path Buttons
* Move FindXCB.cmake to lxqt-build-tools
* Adds superbuild/intree support
* Removes not needed dependency check
* Set CMP0024 policy usage to NEW
* Updates target_include_directories() stuff
* Drops GLib library detection
* Use the new FindMenuCache CMake module
* Use the new FindFm CMake module
* Check for FolderModelItem info (and FmPath)
* Add Fm::PathBar::editingFinished() signal.
* Select the current path when editing the path bar.
* Enable path editing and popup menu for the button-style path bar.
* Properly set styles of path buttons.
* Remove unnecessary debug messages.
* Try to implement the Fm::PathBar which shows a filesystem path as buttons.
* Adds Build PROJECT_NAME with Qt version message
* Move LIBFM_DATA_DIR to pcmanfm repo.
* Refactors CUSTOM_ACTIONS compile definition
* Refactors LIBFM_DATA_DIR compile definition
* Drop add_definitions. Use target_compile_definitions.
* Removes duplicated symbols visibility settings
* README.md: Add build dependency lxqt-build-tools
* Use the new lxqt-build-tools package
* Restore symlink emblem
* Remove empty files
* Try to refactor the emblemed icon support in a more generalized way. Reuse FolderItemDelegate to paint the emblemed icons in Fm::PlacesView to prevent code duplication. APIs changes: * Add Fm::IconTheme::emblems() and cache emblem info in the cache. * Add Fm::FolderItemDelegate::setFileInfoRole() and Fm::FolderItemDelegate::setFmIconRole() * Cache multiple emblems rather than getting the first one only (but only paint the first one now). * Remove icon sizes from Fm::PlacesModel and Fm::PlacesModelItems to maintain MVC design pattern and backward incompatibility. * Expose two role IDs in Fm::PlacesModel: FileInfoRole and FmIconRole so the views can access these data.
* Show File Emblems
* Emblem For (Encrypted) Volume Icons
* Remove cpack (#44)
* Also Consider GEmblemedIcon (#41)
0.11.1 / 2016-09-24
===================
* Release 0.11.1: Add changelog
* Bump version to 0.11.1 (#39)
* Fix Custom Actions Submenu (#38)
* Extend README.md
* Add C++ wrappers for libfm C APIs.
* Correct positioning of small icons in icon/thumbnail mode (#32)
* Silence new compiler warnings (#36)
* Adapt to QT_USE_QSTRINGBUILDER macro
* Fixes xcb linking error
* Use LXQtCompilerSettings cmake module
* Replaces deprecated QStyleOptionViewItemV4
* Fix item text direction with RTL layout (#33)
* Set tabstops for mount operation password dialog (#31)
* Fix memory leak in file open menu (#29)
* Fixes https://github.com/lxde/pcmanfm-qt/issues/351. (#27)
* build: Use external translations
* ts-file removal (#26)
* Fix memory leaks in file menu (#25)
* Merge pull request #24 from philippwiesemann/fix-folder-menu-leak
* translations: fix four typos in messages (#23)
* Fix memory leak if restoring files from trash
* Fix rename dialog displaying source info twice
* Enable renaming in Properties dialog Fixes https://github.com/lxde/pcmanfm-qt/issues/324.
* Cancel indexing job when closing filepropsdialog
* Bump year
0.11.0 / 2016-02-27
===================
* Bump version number and ABI version and preparing for the initial release.
* Redirect focus operation on the folderview to the actual widget
* Use grid layout to have proper properties alignment
* Focus automatically on the first field of the filesearch dialog
* Add (folder) custom actions to foldermenu.
* Update czech translation (by Petr Balíček <pbalicek@seznam.cz>)
* Fix compiling with Qt < 5.4
* Add supports for freedesktop Xdnd direct save (XDS) protocol (closes #pcmanfm-qt/298). * Move Xdnd workarounds from pcmanfm-qt to libfm-qt.
* Protected FolderView methods for getting and setting item delegate margins
* Perform auto-completion for the path bar when the user press Tab key. This closes pcmanfm-qt/#201.
* Disable unused options in file search dialog
* Let the style engine draw the text selection rectangle.
* Fix file search with smaller than option
* Fix target language in Lithuanian translation file
* Fix memory leak if reading stream for image failed
* Update German translation
* Polish translation updated
* Polish translation updated
* Add a missing type casting to fix a compiler warning.
* Relicense libfm-qt to LGPL v.2.1.
* Avoid using blocking calls when initializing the trash can to speed up startup.
* Updated Russian translation Removed ru_RU file
* Create DnD menu with supported actions only
* make createAction public so can be hidden from view
* Adds Runtime and Devel install COMPONENT
* Quote everything that could break due to whitespaces
* Use CMake GenerateExportHeader to generate ABI header file
* Use no exceptions also with Clang
* Uses CMAKE_VISIBILITY and CMAKE_CXX_VISIBILITY flags
* Use QString() instead of ""
* Fix duplicated mtp mounts in the side pane by adding workarounds for potential bugs of gvfs.
* Replace g_io_scheduler which is deprecated with QThread in Fm::PathEdit. * Fix compiler warnings.
* Adds .gitignore
* Makes Release the default BUILD_TYPE
* Adds INSTALL package configuration file
* Removes XCB from the dependencies
* Adds target_include_directories() for the INSTALL_INTERFACE.
* Removes CMAKE_CURRENT_BINARY_DIR usage
* Removes QTX_INCLUDE_DIRS
* Removes Qt Creator .user file
* Updates libraries dependencies
* Adds REQUIRED_PACKAGE_VERSION variables
* Adds generation of TARGETS CMake file
* Creates and installs an package version file
* Renames and moves LIBRARY_NAME variable to the top CMakeLists
* Removes sub-project
* Rename the library to libfm-qt and fix installed header files.
* Split pcmanfm-qt into two parts and move libfm-qt to its own repository.
* Update French translation
* Italian translation updates
* Fix a crash triggered when unmounting a volume from the side pane.
* Avoid duplicated volumes and mounts in the side panel. (This fixes the mtp:// problem.)
* Fix missing null pointer check in Fm::Path::parent() and use nullptr instead of NULL in path.h.
* Code cleanup, «using» directive only if necessary
* Upgrade of pcmanfm-qt to C++11
* hu translations updated
* Fixed several problems with item selection and alignment
* Remove unnecessary qDebug traces
* Update translations.
* The signal QAbstractItemView::iconSizeChanged is only available after Qt 5.5. Add workarounds for older Qt versions.
* Add more null pointer checks in the thumbnail loader to avoid crashes caused by older versions of libfm.
* Update translations
* Avoid the column resizing tricks for the detailed list view when the view contains no columns.
* Improve the column width computation for the detailed view
* PlacesView: activate on click on the second column
* SidePane: reduce size of button's column width
* Added a filterbar + Handle virtually hidden files
* Russian translation update
* Update cs_CZ translation with the strings provided by petrbal in pull request #218.
* Allow adding or removing paths in the file search dialog. Fix bugs in searching for documents.
* Try to implement file searching by using the new Fm::FileSearchDialog class.
* Fix a incorrecy free() in fm_search_free() API.
* Add Fm::Path::take() API to take the ownership of a raw FmPath pointer.
* Add class Fm::FileSearchDialog used to show a dialog for searching files.
* Add FmSearch API which is used to build a search:// URI. (implemented in C and might become part of libfm later).
* Fix #195 - Crash when rightclick on item in trash.
* Add a null check for FmFileInfo in Fm::ProxyFolderModel::lessThan(). This closes #205.
* Fix (workaround) for right-click crash in placesview.
* Russian translation: update
* Italian translation: add desktop entry files, adjust TS files
* placesview: middle-click correct item to activate (fix of segfault)
* Check for null pointers.
* Select the folder from where we have gone up.
* Paste into folder from its context menu.
* libfm-qt: updated german translation
* libfm-qt: lupdated translation files
* Add Greek (el) translation
* added support for mouse back/forward buttons
* Update German translation
* Add new signal prepareFileMenu() to Fm::SidePane and Fm::DirTree so there's a chance to customize the file menu before its shown.
* Port some missing config options from the gtk+ version of pcmanfm.
* Also show hidden dirs in the directory tree when the "Show Hidden" option in the menu is turned on.
* Fix #190 - Column layout is not always updated.
* Create New menu actions, context menu in tree side pane, #163.
* Store side pane mode setting, #157.
* Fixes an translation regression
* Updates translations
* Uses LXQt lxqt_translate_ts() to handle translations
* Fix lxde/lxqt#447 - missing actions in Places' context menus
* Remove trailing whitespaces
* polishing German translation
* Add menu items and shortcuts for new folder and blank file, fixes #163.
* Display folders first when active and sort order descending, fixes #179.
* Avoid space wasted by incorrect decoration in detailed view columns, fixes #177.
* Avoid flickering column header while resizing manually, fixes #176.
* Hungarian translation
* Fix #627 - long startup time. (This blocking is caused by checking the availability of "network:///".)
* Enable text selection in file properties dialog
* Fix some memory leaks reported by valgrind.
* Fix warnings reported by cppcheck.
* Fix warnings reported by scan-build.
* Sort indicators in detailed view, store column and order in settings, fixes #109.
* Fix lxde/lxqt#512 - pcmanfm-qt: cannot delete to trash.
* Polish translations added
* Use 'user-trash' icon for 'Move to Trash'
* The "Custom" option in the application chooser combo box inside file properties dialog is broken. Fix by preventing recursive signal handler invocation.
* The file property dialog does not show correct default applications. Fix a bug in AppChooserComboBox::setMimeType() that does incorrect app comparison.
* When converting an UID or GID to its name, show the number as string when the user or group does not exists.
* Add more null checks.
* Portuguese update
* Add very basic "remaining time" display to the progress dialog. Fix lxde/lxqt#463 - Progress dialog of pcmanfm-qt does not show remaining time.
* Fix lxde/pcmanfm-qt#120 - Foucs "Rename" button when file name changed.
* Remove unnecessary '\n' charactor from the translated strings.
* Fix translations (the newly added string comes from the translation of libfm).
* Improve trash can handling: provide an option to delete the files if moving to trashcan fails.
* Fix broken filenames of translation files.
* More migration to Qt5 new signal/slot syntax for better type safety & speed.
* Migrade to new Qt5 signal/slot syntax for better type safety and speed.
* Fix the broken sorting option.
* Fix lxde/lxqt#448 - PCmanFM-QT renaming place bookmarks does nothing.
* Support linguistic sorting of file names. This fixes #105.
* Update the folder model & view after files are changed.
* Open folders in new tabs by middle clicking on items in the side pane.
* Portuguese update
* Fix a crash of the context menu of places view caused by change of items.
* Save the result of "Edit bookmarks" to gtk+3 bookmarks file instead of the deprecated ~/.gtkbookmarks file. This fixes bug #112 partially.
* Add spanish translations
* Update Japanese translation
* Add German translation
* add Japanese translation
* Implement "UnTrash" for files in trash:/// and close lxde/lxqt#136.
* Add Russian translation
* Drop Qt4 support in code
* Clean up CMakeLists.txt and drop Qt4 support
* New files added from LXDE Pootle server based on templates
* Commit from LXDE Pootle server by user Julius22.: 1007 of 1008 strings translated (2 need review).
* Commit from LXDE Pootle server by user Julius22.: 709 of 1008 strings translated (2 need review).
* Commit from LXDE Pootle server by user mbouzada.: 364 of 364 strings translated (0 need review).
* New files added from LXDE Pootle server based on templates
* Add cs_CZ translation for libfm-qt.
* Commit from LXDE Pootle server by user dforsi.: 364 of 364 strings translated (0 need review).
* Commit from LXDE Pootle server by user dforsi.: 358 of 364 strings translated (0 need review).
* Bump package version number and library soversion to prepare for 0.2 release.
* Fix #85 - Scrolling doesn't work in compact view.
* Hide UI elements that are not usable and disable trash can when gvfs is not available. * Add new API Fm::isUriSchemeSupported().
* Avoid showing the popup menu when moving desktop items.
* Improve handling of file selection and fixing FolderView::selectAll(), which is reported in #45. Delay the handling of selectionChanged() signal to avoid too frequent UI updates.
* Little adjustment for the grid of the folder view to decrease unnecessary margins.
* Use a new way to optimize the size of filename display based on current view mode and font size. This also fixes lxde/lxde-qt #198 - PCmanFM-qt incorrectly displays 256x256 Thumbnails.
* Fully support single click activation and auto-selection with associated options added to the preference dialog.
* Add single click and auto-selection on hover support to Fm::FolderView.
* New files added from LXDE Pootle server based on templates
* New files added from LXDE Pootle server based on templates
* Improve update of translations to avoid unnecessary regeneration of ts files.
* Improve handling of fallback icons. This closes bug #57.
* Translations are lost accidentally in a previous commit. Restore them all.
* Fix a crash in Fm::PlacesModel when gvfs is not available. This closes bug #35 - Ctrl+W closes all windows.
* Do not detect filename extension and select the whole filename by default while renaming directories. This closes bug #71 - Don't try to detect extensions on directories. * API changed: Fm::renameFile() now accepect FmFileInfo as its first parameter.
* Fix bug #80 - make execute in context menu doesn't do change permissions.
* Revert "fixed selection issue #45" This patch breaks copying files by DND in icon view mode and moving desktop icons.
* Use qss instead of QPalette to set the background color of ColorButton. This fixed bug #192 of lxde-qt.
* Rename the library from libfm-qt to libfm-qt5 when built with Qt5.
* fixed selection issue #45
* Fix middle click position calculation in detailed view mode
* Fix crash when context menu is requested but selection is empty
* Activate view items only if clicked with left mouse button
* Do not emit activated signal when keyboard modifiers are on.
* Splits the checks for needed libraries
* Removes duplicated include_directories() entry
* Make sure clang compiler does not complain
* Install pkgconfig file of libfm-qt to correct location in FreeBSD
* Fix missing return values in several methods.
* Avoid endless popups of error dialogs when there are errors launching files.
* Save thumbnails as png files correctly.
* Remember custom positions for desktop icons and fix #29.
* Add template support to the folder context menus and fix #39.
* Show "owner" in the detailed list view mode. * Fix a crash when switching to detailed list mode in qt5.
* Use xcb to set EWMH window type hint to the desktop window in Qt5. * Some more cleanup for the CMakeList.txt files
* Add initial support for Qt5.
* Try to fix #36 again.
* Fix a seg fault caused by the widget being deleted during glib signal handling.
* Code cleanup, removing unnecessary header inclusion to speed up compilation.
* Avoid further handling of MountOperation in the gio finished callback if the object is deleted.
* Use modeless dialogs for app chooser and error reporting in Fm::FileLauncher and Fm::FileMenu.
* Add an small emblem for symlinks (using icon name "emblem-symbolic-link"). Fix bug #27.
* Add missing file to git.
* Move internal implementation details to private headers which are not installed to the system.
* Implement Fm::AppChooserDialog and Fm::AppMenuView classes. * Add <Open with...>/<Other Applications> menu item to Fm::FileMenu. * Add custom app to Fm::AppChooserComboBox.
* Add Fm::AppChooserComboBox and use it in Fm::FilePropsDialog.
* Redesign Fm::FileLauncher APIs to make it more usable. * Add Fm::FileMenu::setFileLauncher() and Fm::FolderView::setFileLauncher() APIs. * Move PCManFM::View::onFileClick() and other popup menu handling to Fm::FolderView.
* Improve Fm::FileLaucher to make it easy to derive subclasses. * Implement a special dialog for opening executable files (Fix bug #13 - it does not launch executables)
* Fix bug #28 - Tash can icon does not refresh when the trash can changes its empty/full status
* Load autocompletion list for Fm::PathEdit only when the widget has the keyboard focus to avoid unnecessary I/O.
* Add proper popup menu items for selected folders and fix #20 and #19. * Some code refactors, adding openFolders() and openFolderInTerminal() to Application class.
* Fix #25 - Amount of items in the folder is not refreshed when the folder content changes. * Update status bar text properly on switching tab pages, selection changes, and folder content changes.
* Fix the broken compiler definitions caused by previous commit.
* Fix bug #22 - Do not select file extension by default on file rename. * Avoid installing private headers (*_p.h files)
* Setup pcmanfm-qt to support optional Custom Actions Menubar detects if libfm was built with vala or not if so a fm-actions.h will exist and support will be compiled in if not, will still compile with no actions menu
* Allow installation path configuration with standard CMake X_INSTALL_DIR
* Support reordering bookmark items in the places view with DND.
* Support adding bookmarks to the places view using drag and drop
* Preparing for implementing dnd for places view.
* Improve the usability of icon view mode, fixing github bug #24.
* Fix crashes caused by invalid pointer access.
* Switch current dir of the folder view correctly with dir tree view in the side pane.
* Finish chdir operation for Fm::DirTreeView.
* Support hiding hidden folders from DirTreeModel.
* Move some methods from DirTreeModel to DirTreeModelItem and fix some row updating problems.
* Implement dynamic folder loading/unloading when expanding or collapsing dir tree nodes. * Enable horizontal scrollbar of dir tree view.
* Move some code from Fm::DirTreeModel to Fm::DirTreeModelItem.
* Partially implement Fm::DirTreeView and Fm::DirTreeModel. (not finished yet)
* Fix an invalid pointer
* Implment different modes for Fm::SidePane, matching libfm-qtk design. * Add basic skeleton for dir tree view/model.
* Fix the cosmetic defect introduced by the eject buttons in the places view.
* Add eject buttons to mounted volumes in the places side pane.
* Add a wrapper class Fm::Path for FmPath C struct.
* Initialize icon_ member of PlacesModelItem correctly.
* Fix fallback icon when a platform plugin is abscent. * Make Fm::IconTheme::checkUpdate() a static function.
* Remove xsettings support. Use a cleaner way to detect config changes by monitor StyleChange events. * Add Fm::IconTheme::changed() signal which is emitted when the icon theme name is changed. * Replace nested Fm::PlacesModel::Item and related classes with their own separate toplevel classes.
* Fix the icon for files of unknown mime types, again.
* Fix the icon for files of unknown mime types.
* Add DES-EMA custom actions to the popup menus.
* Make it safe to create multiple Fm::LibFmQt objects and only initialize libfm once.
* Fix incorrect export symbols and use GNUInstallDirs for installation destination
* Use the latest libfm thumbnailer APIs.
* Fix #3614873 - Thumbnails in icon view shown upside down for some jpegs.
* Adopt recent changes of libfm. FmIcon is essentially identical to GIcon now.
* Add a UI file for application chooser dialog.
* Correctly handle display names of folders in path entry auto-completion.
* Add a global header and add proper definition for LIBFM_QT_API macro.
* Add "Empty trash" and fix a memory leak.
* Fix memory leaks for bookmarks. Fix the broken "Network" item in places.
* Reduce memory usage: Paint the folder items with our own code instead of using a dirty hacks duplicating pixmaps.
* Reduce of size of QPixmapCache in the hope of decreasing memory usage.
* Add fallback icons for places item "applications" and "network".
* Add class Fm::CachedFolderModel, a convinient way to share Fm::FolderModel objects and reduce memory usage.
* Resize the columns of detailed list view when items are inserted or removed.
* Optimize column widths in detailed list mode when the view is resized.
* Only show thumbnails for the first column in detailed list mode.
* Use new "automoc" feature of cmake 2.8.6 and remove cumbersome #include "*.moc" code.
* Trivial fix.
* Add additional custom filter support to ProxyFolderModel.
* Fix some memory leaks.
* Fix some compiler errors and update translations.
* Support the latest libfm trunk. Remove -fpermissive compiler flag and fix compiler errors/warnings.
* Adopt new libfm thumbnail APIs.
* Add soname 0.0.0 for libfm-qt, preparing for 0.1 release.
* Fix crashes caused by incorrect deletion of dialog objects.
* Enable thumbnail related settings.
* Update zh_TW translations and translation templates.
* Add Portuguese translation (pt).
* Add Lithuanian translation (lt_LT).
* Adopt the latest thumbnail API in libfm (thumbnail branch) to speed up loading.
* Workardound incorrect thumbnail painting caused by bug of QStyledItemDelegate. :-(
* Fix a crash caused by accessing data for invalid model index.
* Fix a crash caused by accessing data for invalid model index.
* Add basic thumbnail support (need the latest thumbnail branch of libfm).
* Add archiver integration for file context menus.
* Add archiver integration to file context menus.
* Add mnemonics for menu items. Make confirm dialog before delete and trash can optional.
* Update side pane according to current dir. Little fix.
* Implement "Open in Terminal" and "Open as Root".
* Implement "Auto Run" for newly inserted removable devices.
* Add "Edit Bookmarks" dialog.
* Implement "Invert Selection". Little fix of UI, add a Tool menu to main window.
* Implement "Create New" menu in the folder popup menu.
* Modify make rules for translations. Avoid deleting generated ts files when "make clean". Fix a small error in zh_TW translation.
* Add auto-completion to path entry bar.
* Rename Fm::Application to Fm::LibFmQt to decrease confusion. Set required Qt version to 4.6.
* Load translation files correctly for pcmanfm-qt and libfm-qt.
* Add basic skeleton for i18n (using Qt QTranslator & Qt Linguist).
* Add separate CMakeLists.txt files for pcmanfm and libfm-qt. Hide more implementation details from libfm-qt headers.
* Fix copyright notice in all source files.
* Install a pkgconfig file for libfm-qt for use in other projects.
* Fix a memory error caused by incorrect array size. Fix incorrect spacing of icons.
* Finish chown and chmod supports.
* Try to add file opermission settings UI.
* Implement very basic drag and drop support.
* Supress the incorrect default dnd handling of QListView.
* Try to implement Dnd.
* Finish desktop preferences.
* Improve desktop preferences and apply settings (partially done).
* Add desktop preferences dialog.
* Apply side pane icon size correctly. Add basic skeleton for archiver integration.
* Set shortcuts for frequently used menu options. Implement "rename file" support. Hide tabs when there is only one tab left (optional).
* Delete windows properly when they're closed with setAttribute(Qt::WA_DeleteOnClose); Apply settings to windows after clicking OK in the preference dialog.
* Improve preferences dialog. Change base class of SidePane to QWidget.
* Sync the state of folder popup menu and main menu bar.
* Implement sort options for main window.
* Fix file sorting options for Fm::FolderMenu.
* Correctly implement browse history and fix crashes.
* Add very simple browse history (back/forward) handling.
* Apply gcc visiblility attributes to export less symbols.
* Correctly handle file rename/overwrite during file operations.
* Exclude unnecessary files from CPack.
* Improve folder popup menu.
* Add folder popup menu. Some UI polishing.
* Fix a little crash.
* Fix crashes when turning off desktop manager.
* Show popup menu for blank area of folders.
* Do some refactor to make Fm::FolderView cleaner. Add PCManFM::View to do file manager-specific operations.
* Move files for libfm-qt and pcmanfm-qt to separate subdirs.

@ -1,90 +0,0 @@
cmake_minimum_required(VERSION 3.0.2)
project(libfm-qt)
set(LIBFM_QT_LIBRARY_NAME "fm-qt" CACHE STRING "fm-qt")
set(LIBFM_QT_VERSION_MAJOR 0)
set(LIBFM_QT_VERSION_MINOR 13)
set(LIBFM_QT_VERSION_PATCH 1)
set(LIBFM_QT_VERSION ${LIBFM_QT_VERSION_MAJOR}.${LIBFM_QT_VERSION_MINOR}.${LIBFM_QT_VERSION_PATCH})
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
# We use the libtool versioning scheme for the internal so name, "current:revision:age"
# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
# https://www.sourceware.org/autobook/autobook/autobook_91.html
# http://pusling.com/blog/?p=352
# Actually, libtool uses different ways on different operating systems. So there is no
# universal way to translate a libtool version-info to a cmake version.
# We use "(current-age).age.revision" as the cmake version.
# current: 5, revision: 0, age: 0 => version: 5.0.0
set(LIBFM_QT_LIB_VERSION "5.0.0")
set(LIBFM_QT_LIB_SOVERSION "5")
set(REQUIRED_QT_VERSION "5.7.1")
set(REQUIRED_LIBFM_VERSION "1.2.0")
set(REQUIRED_LIBMENUCACHE_VERSION "0.4.0")
set(REQUIRED_LXQT_BUILD_TOOLS_VERSION "0.5.0")
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
find_package(Qt5Widgets "${REQUIRED_QT_VERSION}" REQUIRED)
find_package(Qt5LinguistTools "${REQUIRED_QT_VERSION}" REQUIRED)
find_package(Qt5X11Extras "${REQUIRED_QT_VERSION}" REQUIRED)
find_package(lxqt-build-tools "${REQUIRED_LXQT_BUILD_TOOLS_VERSION}" REQUIRED)
find_package(Fm "${REQUIRED_LIBFM_VERSION}" REQUIRED)
find_package(MenuCache "${REQUIRED_LIBMENUCACHE_VERSION}" REQUIRED)
find_package(Exif REQUIRED)
find_package(XCB REQUIRED)
message(STATUS "Building ${PROJECT_NAME} with Qt ${Qt5Core_VERSION}")
option(UPDATE_TRANSLATIONS "Update source translation translations/*.ts files" OFF)
include(GNUInstallDirs)
include(GenerateExportHeader)
include(CMakePackageConfigHelpers)
include(LXQtPreventInSourceBuilds)
include(LXQtTranslateTs)
include(LXQtTranslateDesktop)
include(LXQtCompilerSettings NO_POLICY_SCOPE)
set(CMAKE_AUTOMOC TRUE)
set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
write_basic_package_version_file(
"${CMAKE_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}-config-version.cmake"
VERSION ${LIBFM_QT_LIB_VERSION}
COMPATIBILITY AnyNewerVersion
)
install(FILES
"${CMAKE_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}-config-version.cmake"
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${LIBFM_QT_LIBRARY_NAME}"
COMPONENT Devel
)
add_subdirectory(src)
add_subdirectory(data)
# add Doxygen support to generate API docs
# References:
# https://majewsky.wordpress.com/2010/08/14/tip-of-the-day-cmake-and-doxygen/
option(BUILD_DOCUMENTATION "Use Doxygen to create the HTML based API documentation" OFF)
if(BUILD_DOCUMENTATION)
find_package(Doxygen REQUIRED)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in" "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile" @ONLY)
add_custom_target(doc ALL
${DOXYGEN_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
COMMENT "Generating API documentation with Doxygen" VERBATIM
)
install(DIRECTORY
"${CMAKE_CURRENT_BINARY_DIR}/docs"
DESTINATION "${CMAKE_INSTALL_DOCDIR}"
COMPONENT Devel
)
endif()

File diff suppressed because it is too large Load Diff

@ -1,458 +0,0 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS

@ -1,45 +0,0 @@
# libfm-qt
## Overview
libfm-qt is the Qt port of libfm, a library providing components to build
desktop file managers which belongs to [LXDE](https://lxde.org).
libfm-qt is licensed under the terms of the
[LGPLv2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html)
or any later version. See file LICENSE for its full text.
## Installation
### Compiling source code
Runtime dependencies are Qt X11 Extras and libfm ≥ 1.2
(not all features are provided by libfm-qt yet).
Additional build dependencies are CMake,
[lxqt-build-tools](https://github.com/lxqt/lxqt-build-tools) and optionally Git
to pull latest VCS checkouts. The localization files were outsourced to
repository [lxqt-l10n](https://github.com/lxqt/lxqt-l10n) so the corresponding
dependencies are needed, too. Please refer to this repository's `README.md` for
further information.
Code configuration is handled by CMake. CMake variable `CMAKE_INSTALL_PREFIX`
has to be set to `/usr` on most operating systems, depending on the way library
paths are dealt with on 64bit systems variables like `CMAKE_INSTALL_LIBDIR` may
have to be set as well.
To build run `make`, to install `make install` which accepts variable `DESTDIR`
as usual.
### Binary packages
Official binary packages are available in Arch Linux, Debian (as of Debian
stretch) and openSUSE (Leap 42.1 and Tumbleweed).
The library is still missing in Fedora which is providing version 0.10.0 of
PCManFM-Qt only so far. This version was still including the code outsourced
into libfm-qt later so libfm-qt will have to be provided by Fedora, too,
as soon as the distribution upgrades to PCManFM-Qt ≥ 0.10.1.
## Development
Issues should go to the tracker of PCManFM-Qt at
https://github.com/lxqt/pcmanfm-qt/issues.

@ -1,41 +0,0 @@
#=============================================================================
# Copyright 2015 Luís Pereira <luis.artur.pereira@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#=============================================================================
@PACKAGE_INIT@
if (CMAKE_VERSION VERSION_LESS 3.0.2)
message(FATAL_ERROR \"fm-qt requires at least CMake version 3.0.2\")
endif()
include(CMakeFindDependencyMacro)
if (NOT TARGET @LIBFM_QT_LIBRARY_NAME@)
if (POLICY CMP0024)
cmake_policy(SET CMP0024 NEW)
endif()
include("${CMAKE_CURRENT_LIST_DIR}/@LIBFM_QT_LIBRARY_NAME@-targets.cmake")
endif()

@ -1,10 +0,0 @@
install(FILES
"archivers.list"
"terminals.list"
DESTINATION "${CMAKE_INSTALL_DATADIR}/libfm-qt"
)
install(FILES
"libfm-qt-mimetypes.xml"
DESTINATION "${CMAKE_INSTALL_DATADIR}/mime/packages"
)

@ -1,35 +0,0 @@
[file-roller]
create=file-roller --add %U
extract=file-roller --extract %U
extract_to=file-roller --extract-to %d %U
mime_types=application/x-7z-compressed;application/x-7z-compressed-tar;application/x-ace;application/x-alz;application/x-ar;application/x-arj;application/x-bzip;application/x-bzip-compressed-tar;application/x-bzip1;application/x-bzip1-compressed-tar;application/x-cabinet;application/x-cbr;application/x-cbz;application/x-cd-image;application/x-compress;application/x-compressed-tar;application/x-cpio;application/x-deb;application/x-ear;application/x-ms-dos-executable;application/x-gtar;application/x-gzip;application/x-gzpostscript;application/x-java-archive;application/x-lha;application/x-lhz;application/x-lzip;application/x-lzip-compressed-tar;application/x-lzma;application/x-lzma-compressed-tar;application/x-lzop;application/x-lzop-compressed-tar;application/x-rar;application/x-rar-compressed;application/vnd.rar;application/x-rpm;application/x-rzip;application/x-tar;application/x-tarz;application/x-stuffit;application/x-war;application/x-xz;application/x-xz-compressed-tar;application/x-zip;application/x-zip-compressed;application/x-zoo;application/zip;multipart/x-zip;
supports_uris=true
[xarchiver]
create=xarchiver --add-to %F
extract=xarchiver --extract %F
extract_to=xarchiver --extract-to %d %F
mime_types=application/x-arj;application/arj;application/x-bzip;application/x-bzip-compressed-tar;application/x-gzip;application/x-rar;application/x-rar-compressed;application/vnd.rar;application/x-tar;application/x-zip;application/x-zip-compressed;application/zip;multipart/x-zip;application/x-7z-compressed;application/x-compressed-tar;application/x-bzip2;application/x-bzip2-compressed-tar;application/x-lzma-compressed-tar;application/x-lzma;application/x-deb;application/deb;application/vnd.debian.binary-package;application/x-xz;application/x-xz-compressed-tar;application/x-rpm;application/x-source-rpm;application/x-lzop;application/x-lzop-compressed-tar;application/x-tzo;application/x-war;application/x-compress;application/x-tarz;application/x-java-archive;application/x-lha;application/x-lhz;
[squeeze]
create=squeeze --new %F
extract=squeeze --extract %F
extract_to=squeeze --extract-to %d %F
mime_types=application/x-bzip-compressed-tar;application/x-bzip2-compressed-tar;application/x-compressed-tar;application/x-tar;application/x-tarz;application/x-tzo;application/x-zip;application/x-zip-compressed;application/zip;application/x-rar;application/vnd.rar;application/x-gzip;application/x-bzip;application/x-lzop;application/x-compress;
[engrampa]
create=engrampa --add %U
extract=engrampa --extract %U
extract_to=engrampa --extract-to %d %U
mime_types=application/x-7z-compressed;application/x-7z-compressed-tar;application/x-ace;application/x-alz;application/x-ar;application/x-arj;application/x-bzip;application/x-bzip-compressed-tar;application/x-bzip1;application/x-bzip1-compressed-tar;application/x-cabinet;application/x-cbr;application/x-cbz;application/x-cd-image;application/x-compress;application/x-compressed-tar;application/x-cpio;application/x-deb;application/x-ear;application/x-ms-dos-executable;application/x-gtar;application/x-gzip;application/x-gzpostscript;application/x-java-archive;application/x-lha;application/x-lhz;application/x-lzip;application/x-lzip-compressed-tar;application/x-lzma;application/x-lzma-compressed-tar;application/x-lzop;application/x-lzop-compressed-tar;application/x-rar;application/x-rar-compressed;application/vnd.rar;application/x-rpm;application/x-rzip;application/x-tar;application/x-tarz;application/x-stuffit;application/x-war;application/x-xz;application/x-xz-compressed-tar;application/x-zip;application/x-zip-compressed;application/x-zoo;application/zip;multipart/x-zip;
supports_uris=true
# The KDE archiver Ark
# Here we use %F instead of %U since KDE programs do not know the URI provided by gvfs.
# GIO will pass FUSE-based file paths to the KDE programs, which should still work.
[ark]
create=ark --add --dialog %F
extract=ark --batch --dialog %F
extract_to=ark --batch --destination %d %F
mime_types=application/x-tar;application/x-compressed-tar;application/x-bzip-compressed-tar;application/x-tarz;application/x-xz-compressed-tar;application/x-lzma-compressed-tar;application/x-deb;application/x-cd-image;application/x-bcpio;application/x-cpio;application/x-cpio-compressed;application/x-sv4cpio;application/x-sv4crc;application/x-rpm;application/x-source-rpm;application/vnd.ms-cab-compressed;application/x-servicepack;application/x-rar;application/vnd.rar;application/x-7z-compressed;application/x-java-archive;application/zip;application/x-compress;application/x-gzip;application/x-bzip;application/x-bzip2;application/x-lzma;application/x-xz;application/lha;application/x-lha;application/maclha;
supports_uris=true

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Additional mime-types provided by libfm, adding some
missing but frequently seen globs for some common mime-types.
-->
<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>
<mime-type type="text/plain">
<glob pattern="*.ini"/>
<glob pattern="*.inf"/>
</mime-type>
<mime-type type="application/x-ms-dos-executable">
<glob pattern="*.com" />
</mime-type>
<mime-type type="application/x-ms-win-installer">
<comment>Windows installer</comment>
<comment xml:lang="zh_TW">Windows 安裝程式</comment>
<glob pattern="*.msi" />
</mime-type>
<mime-type type="application/x-vbscript">
<comment>MS VBScript</comment>
<glob pattern="*.vbs" />
</mime-type>
<mime-type type="text/x-csharp">
<comment xml:lang="en">C# source</comment>
<comment xml:lang="zh_TW">C# 程式碼</comment>
<glob pattern="*.cs"/>
</mime-type>
<mime-type type="application/x-desktop">
<comment xml:lang="zh_TW">應用程式捷徑</comment>
</mime-type>
<mime-type type="application/x-sharedlib">
<!--
This pattern matching is not very accurate ,but the probability that
a file is named like this and it's not a shared lib, is very low.
-->
<glob pattern="*.dll"/> <!-- Windows dll are shared libs, too -->
<glob pattern="*.so.[0-9]" />
<glob pattern="*.so.[0-9].*" />
</mime-type>
<mime-type type="application/x-desktop">
<glob pattern="*.directory"/>
</mime-type>
</mime-info>

@ -1,80 +0,0 @@
[xterm]
open_arg=-e
noclose_arg=-hold -e
desktop_id=xterm.desktop
[uxterm]
open_arg=-e
noclose_arg=-hold -e
[lxterminal]
open_arg=-e
desktop_id=lxterminal.desktop
[konsole]
open_arg=-e
noclose_arg=--noclose -e
desktop_id=konsole.desktop
[xfce4-terminal]
open_arg=-x
noclose_arg=--hold -x
desktop_id=xfce4-terminal.desktop
[terminator]
open_arg=-x
desktop_id=terminator.desktop
[rxvt]
open_arg=-e
[urxvt]
open_arg=-e
noclose_arg=-hold -e
desktop_id=rxvt-unicode.desktop
[eterm]
open_arg=-e
noclose_arg=--pause -e
desktop_id=eterm.desktop
[gnome-terminal]
open_arg=-x
desktop_id=gnome-terminal.desktop
[wterm]
open_arg=-e
[roxterm]
open_arg=-e
desktop_id=roxterm.desktop
[sakura]
open_arg=-e
desktop_id=sakura.desktop
[qterminal]
open_arg=-e
desktop_id=qterminal.desktop
[lilyterm]
open_arg=-e
noclose_arg=--hold -e
desktop_id=lilyterm.desktop
[urxvtc]
open_arg=-e
noclose_arg=-hold -e
[terminology]
open_arg=-e
noclose_arg=--hold -e
desktop_id=terminology.desktop
[termite]
open_arg=-e
noclose_arg=--hold -e
desktop_id=termite.desktop
[kitty]
desktop_id=kitty.desktop

@ -1,269 +0,0 @@
set(libfm_core_SRCS
# core data structures
core/gobjectptr.h
core/filepath.cpp
core/iconinfo.cpp
core/mimetype.cpp
core/fileinfo.cpp
core/folder.cpp
core/filemonitor.cpp
# i/o jobs
core/job.cpp
core/filetransferjob.cpp
core/deletejob.cpp
core/dirlistjob.cpp
core/filechangeattrjob.cpp
core/fileinfojob.cpp
core/filelinkjob.cpp
core/fileoperationjob.cpp
core/filesysteminfojob.cpp
core/job.cpp
core/totalsizejob.cpp
core/trashjob.cpp
core/untrashjob.cpp
core/thumbnailjob.cpp
# extra desktop services
core/bookmarks.cpp
core/basicfilelauncher.cpp
core/volumemanager.cpp
core/userinfocache.cpp
core/thumbnailer.cpp
core/terminal.cpp
core/archiver.cpp
core/templates.cpp
# custom actions
customactions/fileaction.cpp
customactions/fileactionprofile.cpp
customactions/fileactioncondition.cpp
)
set(libfm_SRCS
${libfm_core_SRCS}
libfmqt.cpp
bookmarkaction.cpp
sidepane.cpp
filelauncher.cpp
foldermodel.cpp
foldermodelitem.cpp
cachedfoldermodel.cpp
proxyfoldermodel.cpp
folderview.cpp
folderitemdelegate.cpp
createnewmenu.cpp
filemenu.cpp
foldermenu.cpp
filepropsdialog.cpp
applaunchcontext.cpp
placesview.cpp
placesmodel.cpp
placesmodelitem.cpp
dirtreeview.cpp
dirtreemodel.cpp
dirtreemodelitem.cpp
dnddest.cpp
mountoperation.cpp
mountoperationpassworddialog.cpp
mountoperationquestiondialog.cpp
fileoperation.cpp
fileoperationdialog.cpp
renamedialog.cpp
pathedit.cpp
pathbar.cpp
colorbutton.cpp
fontbutton.cpp
browsehistory.cpp
utilities.cpp
dndactionmenu.cpp
editbookmarksdialog.cpp
execfiledialog.cpp
appchoosercombobox.cpp
appmenuview.cpp
appchooserdialog.cpp
filesearchdialog.cpp
filedialog.cpp
fm-search.c # might be moved to libfm later
xdndworkaround.cpp
)
set(libfm_UIS
file-props.ui
file-operation-dialog.ui
rename-dialog.ui
mount-operation-password.ui
edit-bookmarks.ui
exec-file.ui
app-chooser-dialog.ui
filesearch.ui
filedialog.ui
)
set(LIBFM_QT_DATA_DIR "${CMAKE_INSTALL_FULL_DATADIR}/libfm-qt")
set(LIBFM_QT_INTREE_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/include")
# add translation for libfm-qt
lxqt_translate_ts(QM_FILES
UPDATE_TRANSLATIONS ${UPDATE_TRANSLATIONS}
SOURCES ${libfm_SRCS} ${libfm_UIS}
INSTALL_DIR "${LIBFM_QT_DATA_DIR}/translations"
PULL_TRANSLATIONS ${PULL_TRANSLATIONS}
CLEAN_TRANSLATIONS ${CLEAN_TRANSLATIONS}
TRANSLATIONS_REPO ${TRANSLATIONS_REPO}
TRANSLATIONS_REFSPEC ${TRANSLATIONS_REFSPEC}
)
add_library(${LIBFM_QT_LIBRARY_NAME} SHARED
${libfm_SRCS}
${libfm_UIS}
${QM_FILES}
)
install(EXPORT
"${LIBFM_QT_LIBRARY_NAME}-targets"
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${LIBFM_QT_LIBRARY_NAME}"
COMPONENT Devel
)
target_link_libraries(${LIBFM_QT_LIBRARY_NAME}
Qt5::Widgets
Qt5::X11Extras
${FM_LIBRARIES}
${MENUCACHE_LIBRARIES}
${XCB_LIBRARIES}
${EXIF_LIBRARIES}
)
# set libtool soname
set_target_properties(${LIBFM_QT_LIBRARY_NAME} PROPERTIES
VERSION ${LIBFM_QT_LIB_VERSION}
SOVERSION ${LIBFM_QT_LIB_SOVERSION}
)
target_include_directories(${LIBFM_QT_LIBRARY_NAME}
PRIVATE "${Qt5Gui_PRIVATE_INCLUDE_DIRS}"
PUBLIC
"${FM_INCLUDE_DIRS}"
"${FM_INCLUDE_DIR}/libfm" # to workaround incorrect #include in fm-actions.
"${MENUCACHE_INCLUDE_DIRS}"
"${XCB_INCLUDE_DIRS}"
"${EXIF_INCLUDE_DIRS}"
INTERFACE
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
"$<BUILD_INTERFACE:${LIBFM_QT_INTREE_INCLUDE_DIR}>"
)
target_compile_definitions(${LIBFM_QT_LIBRARY_NAME}
PRIVATE "LIBFM_QT_DATA_DIR=\"${LIBFM_QT_DATA_DIR}\""
"QT_NO_FOREACH"
PUBLIC "QT_NO_KEYWORDS"
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}_export.h"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libfm-qt"
COMPONENT Devel
)
# install include header files (FIXME: can we make this cleaner? should dir name be versioned?)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libfm-qt"
COMPONENT Devel
FILES_MATCHING PATTERN "*.h"
)
generate_export_header(${LIBFM_QT_LIBRARY_NAME}
EXPORT_MACRO_NAME LIBFM_QT_API
)
# InTree build
file(COPY ${CMAKE_CURRENT_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}_export.h
DESTINATION "${LIBFM_QT_INTREE_INCLUDE_DIR}/libfm-qt"
)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/
DESTINATION "${LIBFM_QT_INTREE_INCLUDE_DIR}/libfm-qt"
FILES_MATCHING PATTERN "*.h"
)
configure_package_config_file(
"${PROJECT_SOURCE_DIR}/cmake/fm-qt-config.cmake.in"
"${CMAKE_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}-config.cmake"
INSTALL_DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${LIBFM_QT_LIBRARY_NAME}"
)
install(FILES
"${CMAKE_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}-config.cmake"
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${LIBFM_QT_LIBRARY_NAME}"
COMPONENT Devel
)
# FIXME: add libtool version to the lib (soname) later.
# FIXME: only export public symbols
install(TARGETS ${LIBFM_QT_LIBRARY_NAME}
DESTINATION "${CMAKE_INSTALL_LIBDIR}"
EXPORT "${LIBFM_QT_LIBRARY_NAME}-targets"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
PUBLIC_HEADER
COMPONENT Runtime
)
export(TARGETS ${LIBFM_QT_LIBRARY_NAME}
FILE "${CMAKE_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}-targets.cmake"
EXPORT_LINK_INTERFACE_LIBRARIES
)
# install a pkgconfig file for libfm-qt
set(REQUIRED_QT "Qt5Widgets >= ${REQUIRED_QT_VERSION} Qt5X11Extras >= ${REQUIRED_QT_VERSION}")
configure_file(libfm-qt.pc.in lib${LIBFM_QT_LIBRARY_NAME}.pc @ONLY)
# FreeBSD loves to install files to different locations
# https://www.freebsd.org/doc/handbook/dirstructure.html
if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/lib${LIBFM_QT_LIBRARY_NAME}.pc"
DESTINATION libdata/pkgconfig
COMPONENT Devel
)
else()
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/lib${LIBFM_QT_LIBRARY_NAME}.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
COMPONENT Devel
)
endif()
# prevent the generated files from being deleted during make cleaner
set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM true)
set(TEST_LIBRARIES
Qt5::Core
Qt5::Widgets
${FM_LIBRARIES}
${LIBFM_QT_LIBRARY_NAME}
)
# some simple test cases
add_executable("test-folder"
tests/test-folder.cpp
)
target_link_libraries("test-folder" ${TEST_LIBRARIES})
add_executable("test-folderview"
tests/test-folderview.cpp
)
target_link_libraries("test-folderview" ${TEST_LIBRARIES})
add_executable("test-filedialog"
tests/test-filedialog.cpp
)
target_link_libraries("test-filedialog" ${TEST_LIBRARIES})
add_executable("test-volumemanager"
tests/test-volumemanager.cpp
)
target_link_libraries("test-volumemanager" ${TEST_LIBRARIES})
add_executable("test-placesview"
tests/test-placesview.cpp
)
target_link_libraries("test-placesview" ${TEST_LIBRARIES})

@ -1,183 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AppChooserDialog</class>
<widget class="QDialog" name="AppChooserDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>432</width>
<height>387</height>
</rect>
</property>
<property name="windowTitle">
<string>Choose an Application</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="1">
<widget class="QLabel" name="fileTypeHeader"/>
</item>
<item row="1" column="0" colspan="2">
<widget class="QTabWidget" name="tabWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Installed Applications</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="Fm::AppMenuView" name="appMenuView"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Custom Command</string>
</attribute>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Command line to execute:</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLineEdit" name="cmdLine"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Application name:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="appName"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_5">
<property name="text">
<string>&lt;b&gt;These special codes can be used in the command line:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;%f&lt;/b&gt;: Represents a single file name&lt;/li&gt;
&lt;li&gt;&lt;b&gt;%F&lt;/b&gt;: Represents multiple file names&lt;/li&gt;
&lt;li&gt;&lt;b&gt;%u&lt;/b&gt;: Represents a single URI of the file&lt;/li&gt;
&lt;li&gt;&lt;b&gt;%U&lt;/b&gt;: Represents multiple URIs&lt;/li&gt;
&lt;/ul&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="keepTermOpen">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Keep terminal window open after command execution</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="useTerminal">
<property name="text">
<string>Execute in terminal emulator</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="setDefault">
<property name="text">
<string>Set selected application as default action of this file type</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Fm::AppMenuView</class>
<extends>QTreeView</extends>
<header>appmenuview.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AppChooserDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>227</x>
<y>359</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AppChooserDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>295</x>
<y>365</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>useTerminal</sender>
<signal>toggled(bool)</signal>
<receiver>keepTermOpen</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>72</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>79</x>
<y>282</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -1,143 +0,0 @@
/*
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "appchoosercombobox.h"
#include "appchooserdialog.h"
#include "utilities.h"
#include "core/iconinfo.h"
namespace Fm {
AppChooserComboBox::AppChooserComboBox(QWidget* parent):
QComboBox(parent),
defaultAppIndex_(-1),
prevIndex_(0),
blockOnCurrentIndexChanged_(false) {
// the new Qt5 signal/slot syntax cannot handle overloaded methods by default
// hence a type-casting is needed here. really ugly!
// reference: https://forum.qt.io/topic/20998/qt5-new-signals-slots-syntax-does-not-work-solved
connect((QComboBox*)this, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &AppChooserComboBox::onCurrentIndexChanged);
}
AppChooserComboBox::~AppChooserComboBox() {
}
void AppChooserComboBox::setMimeType(std::shared_ptr<const Fm::MimeType> mimeType) {
clear();
defaultApp_.reset();
appInfos_.clear();
mimeType_ = std::move(mimeType);
if(mimeType_) {
const char* typeName = mimeType_->name();
defaultApp_ = Fm::GAppInfoPtr{g_app_info_get_default_for_type(typeName, FALSE), false};
GList* appInfos_glist = g_app_info_get_all_for_type(typeName);
int i = 0;
for(GList* l = appInfos_glist; l; l = l->next, ++i) {
Fm::GAppInfoPtr app{G_APP_INFO(l->data), false};
GIcon* gicon = g_app_info_get_icon(app.get());
addItem(gicon ? Fm::IconInfo::fromGIcon(gicon)->qicon(): QIcon(), g_app_info_get_name(app.get()));
if(g_app_info_equal(app.get(), defaultApp_.get())) {
defaultAppIndex_ = i;
}
appInfos_.push_back(std::move(app));
}
g_list_free(appInfos_glist);
}
// add "Other applications" item
insertSeparator(count());
addItem(tr("Customize"));
if(defaultAppIndex_ != -1) {
setCurrentIndex(defaultAppIndex_);
}
}
// returns the currently selected app.
Fm::GAppInfoPtr AppChooserComboBox::selectedApp() const {
// the elements of appInfos_ and the combo indexes before "Customize"
// always have a one-to-one correspondence
int idx = currentIndex();
return idx >= 0 && !appInfos_.empty() ? appInfos_[idx] : Fm::GAppInfoPtr{};
}
bool AppChooserComboBox::isChanged() const {
return (defaultAppIndex_ != currentIndex());
}
void AppChooserComboBox::onCurrentIndexChanged(int index) {
if(index == -1 || index == prevIndex_ || blockOnCurrentIndexChanged_) {
return;
}
// the last item is "Customize"
if(index == (count() - 1)) {
/* TODO: let the user choose an app or add custom actions here. */
QWidget* toplevel = topLevelWidget();
AppChooserDialog dlg(mimeType_, toplevel);
dlg.setWindowModality(Qt::WindowModal);
dlg.setCanSetDefault(false);
if(dlg.exec() == QDialog::Accepted) {
auto app = dlg.selectedApp();
if(app) {
/* see if it's already in the list to prevent duplication */
auto found = std::find_if(appInfos_.cbegin(), appInfos_.cend(), [&](const Fm::GAppInfoPtr& item) {
return g_app_info_equal(app.get(), item.get());
});
// inserting new items or change current index will recursively trigger onCurrentIndexChanged.
// we need to block our handler to prevent recursive calls.
blockOnCurrentIndexChanged_ = true;
/* if it's already in the list, select it */
if(found != appInfos_.cend()) {
auto pos = found - appInfos_.cbegin();
setCurrentIndex(pos);
}
else { /* if it's not found, add it to the list */
auto it = appInfos_.insert(appInfos_.cbegin(), std::move(app));
GIcon* gicon = g_app_info_get_icon(it->get());
insertItem(0, Fm::IconInfo::fromGIcon(gicon)->qicon(), g_app_info_get_name(it->get()));
setCurrentIndex(0);
}
blockOnCurrentIndexChanged_ = false;
return;
}
}
// block our handler to prevent recursive calls.
blockOnCurrentIndexChanged_ = true;
// restore to previously selected item
setCurrentIndex(prevIndex_);
blockOnCurrentIndexChanged_ = false;
}
else {
prevIndex_ = index;
}
}
#if 0
/* get a list of custom apps added with app-chooser.
* the returned GList is owned by the combo box and shouldn't be freed. */
const GList* AppChooserComboBox::customApps() {
}
#endif
} // namespace Fm

@ -1,65 +0,0 @@
/*
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef FM_APPCHOOSERCOMBOBOX_H
#define FM_APPCHOOSERCOMBOBOX_H
#include "libfmqtglobals.h"
#include <QComboBox>
#include <libfm/fm.h>
#include <vector>
#include "core/mimetype.h"
#include "core/gioptrs.h"
namespace Fm {
class LIBFM_QT_API AppChooserComboBox : public QComboBox {
Q_OBJECT
public:
~AppChooserComboBox();
explicit AppChooserComboBox(QWidget* parent);
void setMimeType(std::shared_ptr<const Fm::MimeType> mimeType);
const std::shared_ptr<const Fm::MimeType>& mimeType() const {
return mimeType_;
}
Fm::GAppInfoPtr selectedApp() const;
// const GList* customApps();
bool isChanged() const;
private Q_SLOTS:
void onCurrentIndexChanged(int index);
private:
std::shared_ptr<const Fm::MimeType> mimeType_;
std::vector<Fm::GAppInfoPtr> appInfos_; // applications used to open the file type
Fm::GAppInfoPtr defaultApp_; // default application used to open the file type
int defaultAppIndex_;
int prevIndex_;
bool blockOnCurrentIndexChanged_;
};
}
#endif // FM_APPCHOOSERCOMBOBOX_H

@ -1,286 +0,0 @@
/*
* Copyright 2010-2014 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
* Copyright 2012-2013 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "appchooserdialog.h"
#include "ui_app-chooser-dialog.h"
#include <QPushButton>
#include <gio/gdesktopappinfo.h>
namespace Fm {
AppChooserDialog::AppChooserDialog(std::shared_ptr<const Fm::MimeType> mimeType, QWidget* parent, Qt::WindowFlags f):
QDialog(parent, f),
ui(new Ui::AppChooserDialog()),
mimeType_{std::move(mimeType)},
canSetDefault_(true) {
ui->setupUi(this);
connect(ui->appMenuView, &AppMenuView::selectionChanged, this, &AppChooserDialog::onSelectionChanged);
connect(ui->tabWidget, &QTabWidget::currentChanged, this, &AppChooserDialog::onTabChanged);
if(!ui->appMenuView->isAppSelected()) {
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); // disable OK button
}
}
AppChooserDialog::~AppChooserDialog() {
delete ui;
}
bool AppChooserDialog::isSetDefault() const {
return ui->setDefault->isChecked();
}
static void on_temp_appinfo_destroy(gpointer data, GObject* /*objptr*/) {
char* filename = (char*)data;
if(g_unlink(filename) < 0) {
g_critical("failed to remove %s", filename);
}
/* else
qDebug("temp file %s removed", filename); */
g_free(filename);
}
static GAppInfo* app_info_create_from_commandline(const char* commandline,
const char* application_name,
const char* bin_name,
const char* mime_type,
gboolean terminal, gboolean keep) {
GAppInfo* app = nullptr;
char* dirname = g_build_filename(g_get_user_data_dir(), "applications", nullptr);
const char* app_basename = strrchr(bin_name, '/');
if(app_basename) {
app_basename++;
}
else {
app_basename = bin_name;
}
if(g_mkdir_with_parents(dirname, 0700) == 0) {
char* filename = g_strdup_printf("%s/userapp-%s-XXXXXX.desktop", dirname, app_basename);
int fd = g_mkstemp(filename);
if(fd != -1) {
GString* content = g_string_sized_new(256);
g_string_printf(content,
"[" G_KEY_FILE_DESKTOP_GROUP "]\n"
G_KEY_FILE_DESKTOP_KEY_TYPE "=" G_KEY_FILE_DESKTOP_TYPE_APPLICATION "\n"
G_KEY_FILE_DESKTOP_KEY_NAME "=%s\n"
G_KEY_FILE_DESKTOP_KEY_EXEC "=%s\n"
G_KEY_FILE_DESKTOP_KEY_CATEGORIES "=Other;\n"
G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY "=true\n",
application_name,
commandline
);
if(mime_type)
g_string_append_printf(content,
G_KEY_FILE_DESKTOP_KEY_MIME_TYPE "=%s\n",
mime_type);
g_string_append_printf(content,
G_KEY_FILE_DESKTOP_KEY_TERMINAL "=%s\n",
terminal ? "true" : "false");
if(terminal)
g_string_append_printf(content, "X-KeepTerminal=%s\n",
keep ? "true" : "false");
close(fd); /* g_file_set_contents() may fail creating duplicate */
if(g_file_set_contents(filename, content->str, content->len, nullptr)) {
char* fbname = g_path_get_basename(filename);
app = G_APP_INFO(g_desktop_app_info_new(fbname));
g_free(fbname);
/* if there is mime_type set then created application will be
saved for the mime type (see fm_choose_app_for_mime_type()
below) but if not then we should remove this temp. file */
if(!mime_type || !application_name[0])
/* save the name so this file will be removed later */
g_object_weak_ref(G_OBJECT(app), on_temp_appinfo_destroy,
g_strdup(filename));
}
else {
g_unlink(filename);
}
g_string_free(content, TRUE);
}
g_free(filename);
}
g_free(dirname);
return app;
}
inline static char* get_binary(const char* cmdline, gboolean* arg_found) {
/* see if command line contains %f, %F, %u, or %U. */
const char* p = strstr(cmdline, " %");
if(p) {
if(!strchr("fFuU", *(p + 2))) {
p = nullptr;
}
}
if(arg_found) {
*arg_found = (p != nullptr);
}
if(p) {
return g_strndup(cmdline, p - cmdline);
}
else {
return g_strdup(cmdline);
}
}
GAppInfo* AppChooserDialog::customCommandToApp() {
GAppInfo* app = nullptr;
QByteArray cmdline = ui->cmdLine->text().toLocal8Bit();
QByteArray app_name = ui->appName->text().toUtf8();
if(!cmdline.isEmpty()) {
gboolean arg_found = FALSE;
char* bin1 = get_binary(cmdline.constData(), &arg_found);
qDebug("bin1 = %s", bin1);
/* see if command line contains %f, %F, %u, or %U. */
if(!arg_found) { /* append %f if no %f, %F, %u, or %U was found. */
cmdline += " %f";
}
/* FIXME: is there any better way to do this? */
/* We need to ensure that no duplicated items are added */
if(mimeType_) {
MenuCache* menu_cache;
/* see if the command is already in the list of known apps for this mime-type */
GList* apps = g_app_info_get_all_for_type(mimeType_->name());
GList* l;
for(l = apps; l; l = l->next) {
GAppInfo* app2 = G_APP_INFO(l->data);
const char* cmd = g_app_info_get_commandline(app2);
char* bin2 = get_binary(cmd, nullptr);
if(g_strcmp0(bin1, bin2) == 0) {
app = G_APP_INFO(g_object_ref(app2));
qDebug("found in app list");
g_free(bin2);
break;
}
g_free(bin2);
}
g_list_foreach(apps, (GFunc)g_object_unref, nullptr);
g_list_free(apps);
if(app) {
goto _out;
}
/* see if this command can be found in menu cache */
menu_cache = menu_cache_lookup("applications.menu");
if(menu_cache) {
MenuCacheDir* root_dir = menu_cache_dup_root_dir(menu_cache);
if(root_dir) {
GSList* all_apps = menu_cache_list_all_apps(menu_cache);
GSList* l;
for(l = all_apps; l; l = l->next) {
MenuCacheApp* ma = MENU_CACHE_APP(l->data);
const char* exec = menu_cache_app_get_exec(ma);
char* bin2;
if(exec == nullptr) {
g_warning("application %s has no Exec statement", menu_cache_item_get_id(MENU_CACHE_ITEM(ma)));
continue;
}
bin2 = get_binary(exec, nullptr);
if(g_strcmp0(bin1, bin2) == 0) {
app = G_APP_INFO(g_desktop_app_info_new(menu_cache_item_get_id(MENU_CACHE_ITEM(ma))));
qDebug("found in menu cache");
menu_cache_item_unref(MENU_CACHE_ITEM(ma));
g_free(bin2);
break;
}
menu_cache_item_unref(MENU_CACHE_ITEM(ma));
g_free(bin2);
}
g_slist_free(all_apps);
menu_cache_item_unref(MENU_CACHE_ITEM(root_dir));
}
menu_cache_unref(menu_cache);
}
if(app) {
goto _out;
}
}
/* FIXME: g_app_info_create_from_commandline force the use of %f or %u, so this is not we need */
app = app_info_create_from_commandline(cmdline.constData(), app_name.constData(), bin1,
mimeType_ ? mimeType_->name() : nullptr,
ui->useTerminal->isChecked(), ui->keepTermOpen->isChecked());
_out:
g_free(bin1);
}
return app;
}
void AppChooserDialog::accept() {
QDialog::accept();
if(ui->tabWidget->currentIndex() == 0) {
selectedApp_ = ui->appMenuView->selectedApp();
}
else { // custom command line
selectedApp_ = customCommandToApp();
}
if(selectedApp_) {
if(mimeType_ && g_app_info_get_name(selectedApp_.get())) {
/* add this app to the mime-type */
#if GLIB_CHECK_VERSION(2, 27, 6)
g_app_info_set_as_last_used_for_type(selectedApp_.get(), mimeType_->name(), nullptr);
#else
g_app_info_add_supports_type(selectedApp_.get(), mimeType_->name(), nullptr);
#endif
/* if need to set default */
if(ui->setDefault->isChecked()) {
g_app_info_set_as_default_for_type(selectedApp_.get(), mimeType_->name(), nullptr);
}
}
}
}
void AppChooserDialog::onSelectionChanged() {
bool isAppSelected = ui->appMenuView->isAppSelected();
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isAppSelected);
}
void AppChooserDialog::setMimeType(std::shared_ptr<const Fm::MimeType> mimeType) {
mimeType_ = std::move(mimeType);
if(mimeType_) {
QString text = tr("Select an application to open \"%1\" files")
.arg(QString::fromUtf8(mimeType_->desc()));
ui->fileTypeHeader->setText(text);
}
else {
ui->fileTypeHeader->hide();
ui->setDefault->hide();
}
}
void AppChooserDialog::setCanSetDefault(bool value) {
canSetDefault_ = value;
ui->setDefault->setVisible(value);
}
void AppChooserDialog::onTabChanged(int index) {
if(index == 0) { // app menu view
onSelectionChanged();
}
else if(index == 1) { // custom command
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
}
}
} // namespace Fm

@ -1,79 +0,0 @@
/*
* Copyright 2010-2014 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
* Copyright 2012-2013 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef FM_APPCHOOSERDIALOG_H
#define FM_APPCHOOSERDIALOG_H
#include <QDialog>
#include "libfmqtglobals.h"
#include <libfm/fm.h>
#include "core/mimetype.h"
#include "core/gioptrs.h"
namespace Ui {
class AppChooserDialog;
}
namespace Fm {
class LIBFM_QT_API AppChooserDialog : public QDialog {
Q_OBJECT
public:
explicit AppChooserDialog(std::shared_ptr<const Fm::MimeType> mimeType, QWidget* parent = nullptr, Qt::WindowFlags f = 0);
~AppChooserDialog();
virtual void accept();
void setMimeType(std::shared_ptr<const Fm::MimeType> mimeType);
const std::shared_ptr<const Fm::MimeType>& mimeType() const {
return mimeType_;
}
void setCanSetDefault(bool value);
bool canSetDefault() const {
return canSetDefault_;
}
const Fm::GAppInfoPtr& selectedApp() const {
return selectedApp_;
}
bool isSetDefault() const;
private:
GAppInfo* customCommandToApp();
private Q_SLOTS:
void onSelectionChanged();
void onTabChanged(int index);
private:
Ui::AppChooserDialog* ui;
std::shared_ptr<const Fm::MimeType> mimeType_;
bool canSetDefault_;
Fm::GAppInfoPtr selectedApp_;
};
}
#endif // FM_APPCHOOSERDIALOG_H

@ -1,61 +0,0 @@
/*
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "applaunchcontext.h"
#include <QX11Info>
#include <X11/Xlib.h>
typedef struct _FmAppLaunchContext {
GAppLaunchContext parent;
}FmAppLaunchContext;
G_DEFINE_TYPE(FmAppLaunchContext, fm_app_launch_context, G_TYPE_APP_LAUNCH_CONTEXT)
static char* fm_app_launch_context_get_display(GAppLaunchContext * /*context*/, GAppInfo * /*info*/, GList * /*files*/) {
Display* dpy = QX11Info::display();
if(dpy) {
char* xstr = DisplayString(dpy);
return g_strdup(xstr);
}
return nullptr;
}
static char* fm_app_launch_context_get_startup_notify_id(GAppLaunchContext * /*context*/, GAppInfo * /*info*/, GList * /*files*/) {
return nullptr;
}
static void fm_app_launch_context_class_init(FmAppLaunchContextClass* klass) {
GAppLaunchContextClass* app_launch_class = G_APP_LAUNCH_CONTEXT_CLASS(klass);
app_launch_class->get_display = fm_app_launch_context_get_display;
app_launch_class->get_startup_notify_id = fm_app_launch_context_get_startup_notify_id;
}
static void fm_app_launch_context_init(FmAppLaunchContext* /*context*/) {
}
FmAppLaunchContext* fm_app_launch_context_new_for_widget(QWidget* /*widget*/) {
FmAppLaunchContext* context = (FmAppLaunchContext*)g_object_new(FM_TYPE_APP_LAUNCH_CONTEXT, nullptr);
return context;
}
FmAppLaunchContext* fm_app_launch_context_new() {
FmAppLaunchContext* context = (FmAppLaunchContext*)g_object_new(FM_TYPE_APP_LAUNCH_CONTEXT, nullptr);
return context;
}

@ -1,50 +0,0 @@
/*
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef FM_APP_LAUNCHCONTEXT_H
#define FM_APP_LAUNCHCONTEXT_H
#include "libfmqtglobals.h"
#include <gio/gio.h>
#include <QWidget>
#define FM_TYPE_APP_LAUNCH_CONTEXT (fm_app_launch_context_get_type())
#define FM_APP_LAUNCH_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\
FM_TYPE_APP_LAUNCH_CONTEXT, FmAppLaunchContext))
#define FM_APP_LAUNCH_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),\
FM_TYPE_APP_LAUNCH_CONTEXT, FmAppLaunchContextClass))
#define FM_IS_APP_LAUNCH_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),\
FM_TYPE_APP_LAUNCH_CONTEXT))
#define FM_IS_APP_LAUNCH_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),\
FM_TYPE_APP_LAUNCH_CONTEXT))
#define FM_APP_LAUNCH_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),\
FM_TYPE_APP_LAUNCH_CONTEXT, FmAppLaunchContextClass))
typedef struct _FmAppLaunchContext FmAppLaunchContext;
typedef struct _FmAppLaunchContextClass {
GAppLaunchContextClass parent;
}FmAppLaunchContextClass;
FmAppLaunchContext* fm_app_launch_context_new();
FmAppLaunchContext* fm_app_launch_context_new_for_widget(QWidget* widget);
GType fm_app_launch_context_get_type();
#endif // FM_APPLAUNCHCONTEXT_H

@ -1,161 +0,0 @@
/*
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "appmenuview.h"
#include <QStandardItemModel>
#include "appmenuview_p.h"
#include <gio/gdesktopappinfo.h>
namespace Fm {
AppMenuView::AppMenuView(QWidget* parent):
QTreeView(parent),
model_(new QStandardItemModel()),
menu_cache(nullptr),
menu_cache_reload_notify(nullptr) {
setHeaderHidden(true);
setSelectionMode(SingleSelection);
// initialize model
// TODO: share one model among all app menu view widgets
// ensure that we're using lxmenu-data (FIXME: should we do this?)
QByteArray oldenv = qgetenv("XDG_MENU_PREFIX");
qputenv("XDG_MENU_PREFIX", "lxde-");
menu_cache = menu_cache_lookup("applications.menu");
// if(!oldenv.isEmpty())
qputenv("XDG_MENU_PREFIX", oldenv); // restore the original value if needed
if(menu_cache) {
MenuCacheDir* dir = menu_cache_dup_root_dir(menu_cache);
menu_cache_reload_notify = menu_cache_add_reload_notify(menu_cache, _onMenuCacheReload, this);
if(dir) { /* content of menu is already loaded */
addMenuItems(nullptr, dir);
menu_cache_item_unref(MENU_CACHE_ITEM(dir));
}
}
setModel(model_);
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &AppMenuView::selectionChanged);
selectionModel()->select(model_->index(0, 0), QItemSelectionModel::SelectCurrent);
}
AppMenuView::~AppMenuView() {
delete model_;
if(menu_cache) {
if(menu_cache_reload_notify) {
menu_cache_remove_reload_notify(menu_cache, menu_cache_reload_notify);
}
menu_cache_unref(menu_cache);
}
}
void AppMenuView::addMenuItems(QStandardItem* parentItem, MenuCacheDir* dir) {
GSList* l;
GSList* list;
/* Iterate over all menu items in this directory. */
for(l = list = menu_cache_dir_list_children(dir); l != nullptr; l = l->next) {
/* Get the menu item. */
MenuCacheItem* menuItem = MENU_CACHE_ITEM(l->data);
switch(menu_cache_item_get_type(menuItem)) {
case MENU_CACHE_TYPE_NONE:
case MENU_CACHE_TYPE_SEP:
break;
case MENU_CACHE_TYPE_APP:
case MENU_CACHE_TYPE_DIR: {
AppMenuViewItem* newItem = new AppMenuViewItem(menuItem);
if(parentItem) {
parentItem->insertRow(parentItem->rowCount(), newItem);
}
else {
model_->insertRow(model_->rowCount(), newItem);
}
if(menu_cache_item_get_type(menuItem) == MENU_CACHE_TYPE_DIR) {
addMenuItems(newItem, MENU_CACHE_DIR(menuItem));
}
break;
}
}
}
g_slist_free_full(list, (GDestroyNotify)menu_cache_item_unref);
}
void AppMenuView::onMenuCacheReload(MenuCache* mc) {
MenuCacheDir* dir = menu_cache_dup_root_dir(mc);
model_->clear();
/* FIXME: preserve original selection */
if(dir) {
addMenuItems(nullptr, dir);
menu_cache_item_unref(MENU_CACHE_ITEM(dir));
selectionModel()->select(model_->index(0, 0), QItemSelectionModel::SelectCurrent);
}
}
bool AppMenuView::isAppSelected() const {
AppMenuViewItem* item = selectedItem();
return (item && item->isApp());
}
AppMenuViewItem* AppMenuView::selectedItem() const {
QModelIndexList selected = selectedIndexes();
if(!selected.isEmpty()) {
AppMenuViewItem* item = static_cast<AppMenuViewItem*>(model_->itemFromIndex(selected.first()
));
return item;
}
return nullptr;
}
Fm::GAppInfoPtr AppMenuView::selectedApp() const {
const char* id = selectedAppDesktopId();
return Fm::GAppInfoPtr{id ? G_APP_INFO(g_desktop_app_info_new(id)) : nullptr, false};
}
QByteArray AppMenuView::selectedAppDesktopFilePath() const {
AppMenuViewItem* item = selectedItem();
if(item && item->isApp()) {
char* path = menu_cache_item_get_file_path(item->item());
QByteArray ret(path);
g_free(path);
return ret;
}
return QByteArray();
}
const char* AppMenuView::selectedAppDesktopId() const {
AppMenuViewItem* item = selectedItem();
if(item && item->isApp()) {
return menu_cache_item_get_id(item->item());
}
return nullptr;
}
FmPath* AppMenuView::selectedAppDesktopPath() const {
AppMenuViewItem* item = selectedItem();
if(item && item->isApp()) {
char* mpath = menu_cache_dir_make_path(MENU_CACHE_DIR(item));
FmPath* path = fm_path_new_relative(fm_path_get_apps_menu(),
mpath + 13 /* skip "/Applications" */);
g_free(mpath);
return path;
}
return nullptr;
}
} // namespace Fm

@ -1,74 +0,0 @@
/*
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef FM_APPMENUVIEW_H
#define FM_APPMENUVIEW_H
#include <QTreeView>
#include "libfmqtglobals.h"
#include <libfm/fm.h>
#include <menu-cache/menu-cache.h>
#include "core/gioptrs.h"
class QStandardItemModel;
class QStandardItem;
namespace Fm {
class AppMenuViewItem;
class LIBFM_QT_API AppMenuView : public QTreeView {
Q_OBJECT
public:
explicit AppMenuView(QWidget* parent = nullptr);
~AppMenuView();
Fm::GAppInfoPtr selectedApp() const;
const char* selectedAppDesktopId() const;
QByteArray selectedAppDesktopFilePath() const;
FmPath* selectedAppDesktopPath() const;
bool isAppSelected() const;
Q_SIGNALS:
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
private:
void addMenuItems(QStandardItem* parentItem, MenuCacheDir* dir);
void onMenuCacheReload(MenuCache* mc);
static void _onMenuCacheReload(MenuCache* mc, gpointer user_data) {
static_cast<AppMenuView*>(user_data)->onMenuCacheReload(mc);
}
AppMenuViewItem* selectedItem() const;
private:
// gboolean fm_app_menu_view_is_item_app(, GtkTreeIter* it);
QStandardItemModel* model_;
MenuCache* menu_cache;
MenuCacheNotifyId menu_cache_reload_notify;
};
}
#endif // FM_APPMENUVIEW_H

@ -1,71 +0,0 @@
/*
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef FM_APPMENUVIEW_P_H
#define FM_APPMENUVIEW_P_H
#include <QStandardItem>
#include <menu-cache/menu-cache.h>
#include "core/iconinfo.h"
namespace Fm {
class AppMenuViewItem : public QStandardItem {
public:
explicit AppMenuViewItem(MenuCacheItem* item):
item_(menu_cache_item_ref(item)) {
std::shared_ptr<const Fm::IconInfo> icon;
if(menu_cache_item_get_icon(item)) {
icon = Fm::IconInfo::fromName(menu_cache_item_get_icon(item));
}
setText(menu_cache_item_get_name(item));
setEditable(false);
setDragEnabled(false);
if(icon) {
setIcon(icon->qicon());
}
}
~AppMenuViewItem() {
menu_cache_item_unref(item_);
}
MenuCacheItem* item() {
return item_;
}
int type() const {
return menu_cache_item_get_type(item_);
}
bool isApp() {
return type() == MENU_CACHE_TYPE_APP;
}
bool isDir() {
return type() == MENU_CACHE_TYPE_DIR;
}
private:
MenuCacheItem* item_;
};
}
#endif // FM_APPMENUVIEW_P_H

@ -1,32 +0,0 @@
/*
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "bookmarkaction.h"
namespace Fm {
BookmarkAction::BookmarkAction(std::shared_ptr<const Fm::BookmarkItem> item, QObject* parent):
QAction(parent),
item_(std::move(item)) {
setText(item_->name());
}
} // namespace Fm

@ -1,49 +0,0 @@
/*
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef BOOKMARKACTION_H
#define BOOKMARKACTION_H
#include "libfmqtglobals.h"
#include <QAction>
#include "core/bookmarks.h"
namespace Fm {
// action used to create bookmark menu items
class LIBFM_QT_API BookmarkAction : public QAction {
public:
explicit BookmarkAction(std::shared_ptr<const Fm::BookmarkItem> item, QObject* parent = 0);
const std::shared_ptr<const Fm::BookmarkItem>& bookmark() const {
return item_;
}
const Fm::FilePath& path() const {
return item_->path();
}
private:
std::shared_ptr<const Fm::BookmarkItem> item_;
};
}
#endif // BOOKMARKACTION_H

@ -1,93 +0,0 @@
/*
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "browsehistory.h"
namespace Fm {
BrowseHistory::BrowseHistory():
currentIndex_(0),
maxCount_(10) {
}
BrowseHistory::~BrowseHistory() {
}
void BrowseHistory::add(Fm::FilePath path, int scrollPos) {
int lastIndex = items_.size() - 1;
if(currentIndex_ < lastIndex) {
// if we're not at the last item, remove items after the current one.
items_.erase(items_.cbegin() + currentIndex_ + 1, items_.cend());
}
if(items_.size() + 1 > static_cast<size_t>(maxCount_)) {
// if there are too many items, remove the oldest one.
// FIXME: what if currentIndex_ == 0? remove the last item instead?
if(currentIndex_ == 0) {
items_.erase(items_.cbegin() + lastIndex);
}
else {
items_.erase(items_.cbegin());
--currentIndex_;
}
}
// add a path and current scroll position to browse history
items_.push_back(BrowseHistoryItem(path, scrollPos));
currentIndex_ = items_.size() - 1;
}
void BrowseHistory::setCurrentIndex(int index) {
if(index >= 0 && static_cast<size_t>(index) < items_.size()) {
currentIndex_ = index;
// FIXME: should we emit a signal for the change?
}
}
bool BrowseHistory::canBackward() const {
return (currentIndex_ > 0);
}
int BrowseHistory::backward() {
if(canBackward()) {
--currentIndex_;
}
return currentIndex_;
}
bool BrowseHistory::canForward() const {
return (static_cast<size_t>(currentIndex_) + 1 < items_.size());
}
int BrowseHistory::forward() {
if(canForward()) {
++currentIndex_;
}
return currentIndex_;
}
void BrowseHistory::setMaxCount(int maxCount) {
maxCount_ = maxCount;
if(items_.size() > static_cast<size_t>(maxCount)) {
// TODO: remove some items
}
}
} // namespace Fm

@ -1,132 +0,0 @@
/*
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef FM_BROWSEHISTORY_H
#define FM_BROWSEHISTORY_H
#include "libfmqtglobals.h"
#include <vector>
#include <libfm/fm.h>
#include "core/filepath.h"
namespace Fm {
// class used to story browsing history of folder views
// We use this class to replace FmNavHistory provided by libfm since
// the original Libfm API is hard to use and confusing.
class LIBFM_QT_API BrowseHistoryItem {
public:
explicit BrowseHistoryItem():
scrollPos_(0) {
}
explicit BrowseHistoryItem(Fm::FilePath path, int scrollPos = 0):
path_(std::move(path)),
scrollPos_(scrollPos) {
}
BrowseHistoryItem(const BrowseHistoryItem& other) = default;
~BrowseHistoryItem() {
}
BrowseHistoryItem& operator=(const BrowseHistoryItem& other) {
path_ = other.path_;
scrollPos_ = other.scrollPos_;
return *this;
}
Fm::FilePath path() const {
return path_;
}
int scrollPos() const {
return scrollPos_;
}
void setScrollPos(int pos) {
scrollPos_ = pos;
}
private:
Fm::FilePath path_;
int scrollPos_;
// TODO: we may need to store current selection as well.
};
class LIBFM_QT_API BrowseHistory {
public:
BrowseHistory();
virtual ~BrowseHistory();
int currentIndex() const {
return currentIndex_;
}
void setCurrentIndex(int index);
Fm::FilePath currentPath() const {
return items_[currentIndex_].path();
}
int currentScrollPos() const {
return items_[currentIndex_].scrollPos();
}
BrowseHistoryItem& currentItem() {
return items_[currentIndex_];
}
size_t size() const {
return items_.size();
}
BrowseHistoryItem& at(int index) {
return items_[index];
}
void add(Fm::FilePath path, int scrollPos = 0);
bool canForward() const;
bool canBackward() const;
int backward();
int forward();
int maxCount() const {
return maxCount_;
}
void setMaxCount(int maxCount);
private:
std::vector<BrowseHistoryItem> items_;
int currentIndex_;
int maxCount_;
};
}
#endif // FM_BROWSEHISTORY_H

@ -1,67 +0,0 @@
/*
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "cachedfoldermodel.h"
namespace Fm {
CachedFolderModel::CachedFolderModel(const std::shared_ptr<Fm::Folder>& folder):
FolderModel(),
refCount(1) {
FolderModel::setFolder(folder);
}
CachedFolderModel::~CachedFolderModel() {
// qDebug("delete CachedFolderModel");
}
CachedFolderModel* CachedFolderModel::modelFromFolder(const std::shared_ptr<Fm::Folder>& folder) {
QVariant cache = folder->property(cacheKey);
CachedFolderModel* model = cache.value<CachedFolderModel*>();
if(model) {
model->ref();
}
else {
model = new CachedFolderModel(folder);
cache = QVariant::fromValue(model);
folder->setProperty(cacheKey, cache);
}
return model;
}
CachedFolderModel* CachedFolderModel::modelFromPath(const Fm::FilePath& path) {
auto folder = Fm::Folder::fromPath(path);
if(folder) {
CachedFolderModel* model = modelFromFolder(folder);
return model;
}
return nullptr;
}
void CachedFolderModel::unref() {
// qDebug("unref cache");
--refCount;
if(refCount <= 0) {
folder()->setProperty(cacheKey, QVariant());
delete(this);
}
}
} // namespace Fm

@ -1,55 +0,0 @@
/*
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef FM_CACHEDFOLDERMODEL_H
#define FM_CACHEDFOLDERMODEL_H
#include "libfmqtglobals.h"
#include "foldermodel.h"
#include "core/folder.h"
namespace Fm {
// FIXME: deprecate CachedFolderModel later (ugly API design with manual ref()/unref())
class LIBFM_QT_API CachedFolderModel : public FolderModel {
Q_OBJECT
public:
explicit CachedFolderModel(const std::shared_ptr<Fm::Folder>& folder);
void ref() {
++refCount;
}
void unref();
static CachedFolderModel* modelFromFolder(const std::shared_ptr<Fm::Folder>& folder);
static CachedFolderModel* modelFromPath(const Fm::FilePath& path);
private:
virtual ~CachedFolderModel();
void setFolder(FmFolder* folder);
private:
int refCount;
constexpr static const char* cacheKey = "CachedFolderModel";
};
}
#endif // FM_CACHEDFOLDERMODEL_H

@ -1,53 +0,0 @@
/*
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "colorbutton.h"
#include <QColorDialog>
namespace Fm {
ColorButton::ColorButton(QWidget* parent): QPushButton(parent) {
connect(this, &QPushButton::clicked, this, &ColorButton::onClicked);
}
ColorButton::~ColorButton() {
}
void ColorButton::onClicked() {
QColorDialog dlg(color_);
if(dlg.exec() == QDialog::Accepted) {
setColor(dlg.selectedColor());
}
}
void ColorButton::setColor(const QColor& color) {
if(color != color_) {
color_ = color;
// use qss instead of QPalette to set the background color
// otherwise, this won't work when using the gtk style.
QString style = QString("QPushButton{background-color:%1;}").arg(color.name());
setStyleSheet(style);
Q_EMIT changed();
}
}
} // namespace Fm

@ -1,55 +0,0 @@
/*
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef FM_COLORBUTTON_H
#define FM_COLORBUTTON_H
#include "libfmqtglobals.h"
#include <QPushButton>
#include <QColor>
namespace Fm {
class LIBFM_QT_API ColorButton : public QPushButton {
Q_OBJECT
public:
explicit ColorButton(QWidget* parent = 0);
virtual ~ColorButton();
void setColor(const QColor&);
QColor color() const {
return color_;
}
Q_SIGNALS:
void changed();
private Q_SLOTS:
void onClicked();
private:
QColor color_;
};
}
#endif // FM_COLORBUTTON_H

@ -1,174 +0,0 @@
#include "libfmqtglobals.h"
#include "archiver.h"
#include <string.h>
#include <glib.h>
#include <gio/gdesktopappinfo.h>
#include <string>
namespace Fm {
Archiver* Archiver::defaultArchiver_ = nullptr; // static
std::vector<std::unique_ptr<Archiver>> Archiver::allArchivers_; // static
Archiver::Archiver() {
}
bool Archiver::isMimeTypeSupported(const char* type) {
char** p;
if(G_UNLIKELY(!type)) {
return false;
}
for(p = mimeTypes_.get(); *p; ++p) {
if(strcmp(*p, type) == 0) {
return true;
}
}
return false;
}
bool Archiver::launchProgram(GAppLaunchContext* ctx, const char* cmd, const FilePathList& files, const FilePath& dir) {
char* _cmd = NULL;
const char* dir_place_holder;
GKeyFile* dummy;
if(dir.isValid() && (dir_place_holder = strstr(cmd, "%d"))) {
CStrPtr dir_str;
int len;
if(strstr(cmd, "%U") || strstr(cmd, "%u")) { /* supports URI */
dir_str = dir.uri();
}
else {
dir_str = dir.localPath();
}
// FIXME: remove libfm dependency here
/* replace all % with %% so encoded URI can be handled correctly when parsing Exec key. */
std::string percentEscapedDir;
for(auto p = dir_str.get(); *p; ++p) {
percentEscapedDir += *p;
if(*p == '%') {
percentEscapedDir += '%';
}
}
/* quote the path or URI */
dir_str = CStrPtr{g_shell_quote(percentEscapedDir.c_str())};
len = strlen(cmd) - 2 + strlen(dir_str.get()) + 1;
_cmd = (char*)g_malloc(len);
len = (dir_place_holder - cmd);
strncpy(_cmd, cmd, len);
strcpy(_cmd + len, dir_str.get());
strcat(_cmd, dir_place_holder + 2);
cmd = _cmd;
}
/* create a fake key file to cheat GDesktopAppInfo */
dummy = g_key_file_new();
g_key_file_set_string(dummy, G_KEY_FILE_DESKTOP_GROUP, "Type", "Application");
g_key_file_set_string(dummy, G_KEY_FILE_DESKTOP_GROUP, "Name", program_.get());
/* replace all % with %% so encoded URI can be handled correctly when parsing Exec key. */
g_key_file_set_string(dummy, G_KEY_FILE_DESKTOP_GROUP, "Exec", cmd);
GAppInfoPtr app{reinterpret_cast<GAppInfo*>(g_desktop_app_info_new_from_keyfile(dummy)), false};
g_key_file_free(dummy);
g_debug("cmd = %s", cmd);
if(app) {
GList* uris = NULL;
for(auto& file: files) {
uris = g_list_prepend(uris, g_strdup(file.uri().get()));
}
g_app_info_launch_uris(app.get(), uris, ctx, NULL);
g_list_foreach(uris, (GFunc)g_free, NULL);
g_list_free(uris);
}
g_free(_cmd);
return true;
}
bool Archiver::createArchive(GAppLaunchContext* ctx, const FilePathList& files) {
if(createCmd_ && !files.empty()) {
launchProgram(ctx, createCmd_.get(), files, FilePath{});
}
return false;
}
bool Archiver::extractArchives(GAppLaunchContext* ctx, const FilePathList& files) {
if(extractCmd_ && !files.empty()) {
launchProgram(ctx, extractCmd_.get(), files, FilePath{});
}
return false;
}
bool Archiver::extractArchivesTo(GAppLaunchContext* ctx, const FilePathList& files, const FilePath& dest_dir) {
if(extractToCmd_ && !files.empty()) {
launchProgram(ctx, extractToCmd_.get(), files, dest_dir);
}
return false;
}
// static
Archiver* Archiver::defaultArchiver() {
allArchivers(); // to have a preliminary default archiver
return defaultArchiver_;
}
void Archiver::setDefaultArchiverByName(const char *name) {
if(name) {
auto& all = allArchivers();
for(auto& archiver: all) {
if(archiver->program_ && strcmp(archiver->program_.get(), name) == 0) {
defaultArchiver_ = archiver.get();
break;
}
}
}
}
// static
void Archiver::setDefaultArchiver(Archiver* archiver) {
if(archiver) {
defaultArchiver_ = archiver;
}
}
// static
const std::vector<std::unique_ptr<Archiver> >& Archiver::allArchivers() {
// load all archivers on demand
if(allArchivers_.empty()) {
GKeyFile* kf = g_key_file_new();
if(g_key_file_load_from_file(kf, LIBFM_QT_DATA_DIR "/archivers.list", G_KEY_FILE_NONE, NULL)) {
gsize n_archivers;
CStrArrayPtr programs{g_key_file_get_groups(kf, &n_archivers)};
if(programs) {
gsize i;
for(i = 0; i < n_archivers; ++i) {
auto program = programs[i];
std::unique_ptr<Archiver> archiver{new Archiver{}};
archiver->createCmd_ = CStrPtr{g_key_file_get_string(kf, program, "create", NULL)};
archiver->extractCmd_ = CStrPtr{g_key_file_get_string(kf, program, "extract", NULL)};
archiver->extractToCmd_ = CStrPtr{g_key_file_get_string(kf, program, "extract_to", NULL)};
archiver->mimeTypes_ = CStrArrayPtr{g_key_file_get_string_list(kf, program, "mime_types", NULL, NULL)};
archiver->program_ = CStrPtr{g_strdup(program)};
// if default archiver is not set, find the first program existing in the current system.
if(!defaultArchiver_) {
CStrPtr fullPath{g_find_program_in_path(program)};
if(fullPath) {
defaultArchiver_ = archiver.get();
}
}
allArchivers_.emplace_back(std::move(archiver));
}
}
}
g_key_file_free(kf);
}
return allArchivers_;
}
} // namespace Fm

@ -1,69 +0,0 @@
#ifndef ARCHIVER_H
#define ARCHIVER_H
#include "../libfmqtglobals.h"
#include "filepath.h"
#include "gioptrs.h"
#include <vector>
#include <memory>
namespace Fm {
class LIBFM_QT_API Archiver {
public:
Archiver();
bool isMimeTypeSupported(const char* type);
bool canCreateArchive() const {
return createCmd_ != nullptr;
}
bool createArchive(GAppLaunchContext* ctx, const FilePathList& files);
bool canExtractArchives() const {
return extractCmd_ != nullptr;
}
bool extractArchives(GAppLaunchContext* ctx, const FilePathList& files);
bool canExtractArchivesTo() const {
return extractToCmd_ != nullptr;
}
bool extractArchivesTo(GAppLaunchContext* ctx, const FilePathList& files, const FilePath& dest_dir);
/* get default GUI archivers used by libfm */
static Archiver* defaultArchiver();
/* set default GUI archivers used by libfm */
static void setDefaultArchiverByName(const char* name);
/* set default GUI archivers used by libfm */
static void setDefaultArchiver(Archiver* archiver);
/* get a list of FmArchiver* of all GUI archivers known to libfm */
static const std::vector<std::unique_ptr<Archiver>>& allArchivers();
const char* program() const {
return program_.get();
}
private:
bool launchProgram(GAppLaunchContext* ctx, const char* cmd, const FilePathList& files, const FilePath &dir);
private:
CStrPtr program_;
CStrPtr createCmd_;
CStrPtr extractCmd_;
CStrPtr extractToCmd_;
CStrArrayPtr mimeTypes_;
static Archiver* defaultArchiver_;
static std::vector<std::unique_ptr<Archiver>> allArchivers_;
};
} // namespace Fm
#endif // ARCHIVER_H

@ -1,345 +0,0 @@
#include "basicfilelauncher.h"
#include "fileinfojob.h"
#include "mountoperation.h"
#include <gio/gdesktopappinfo.h>
#include <glib/gi18n.h>
#include <unordered_map>
#include <string>
#include <QObject>
#include <QEventLoop>
#include <QDebug>
namespace Fm {
BasicFileLauncher::BasicFileLauncher():
quickExec_{false} {
}
BasicFileLauncher::~BasicFileLauncher() {
}
bool BasicFileLauncher::launchFiles(const FileInfoList& fileInfos, GAppLaunchContext* ctx) {
std::unordered_map<std::string, FileInfoList> mimeTypeToFiles;
FileInfoList folderInfos;
FilePathList pathsToLaunch;
// classify files according to different mimetypes
for(auto& fileInfo : fileInfos) {
// qDebug("path: %s, target: %s", fileInfo->path().toString().get(), fileInfo->target().c_str());
if(fileInfo->isDir()) {
folderInfos.emplace_back(fileInfo);
}
else if(fileInfo->isMountable()) {
if(fileInfo->target().empty()) {
// the mountable is not yet mounted so we have no target URI.
GErrorPtr err{G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED,
QObject::tr("The path is not mounted.")};
if(!showError(ctx, err, fileInfo->path(), fileInfo)) {
// the user fail to handle the error, skip this file.
continue;
}
// we do not have the target path in the FileInfo object.
// try to launch our path again to query the new file info later so we can get the mounted target URI.
pathsToLaunch.emplace_back(fileInfo->path());
}
else {
// we have the target path, launch it later
pathsToLaunch.emplace_back(FilePath::fromPathStr(fileInfo->target().c_str()));
}
}
else if(fileInfo->isDesktopEntry()) {
// launch the desktop entry
launchDesktopEntry(fileInfo, FilePathList{}, ctx);
}
else if(fileInfo->isExecutableType()) {
// directly execute the file
launchExecutable(fileInfo, ctx);
}
else if(fileInfo->isShortcut()) {
// for shortcuts, launch their targets instead
auto path = handleShortcut(fileInfo, ctx);
if(path.isValid()) {
pathsToLaunch.emplace_back(path);
}
}
else {
auto& mimeType = fileInfo->mimeType();
mimeTypeToFiles[mimeType->name()].emplace_back(fileInfo);
}
}
// open folders
if(!folderInfos.empty()) {
GErrorPtr err;
openFolder(ctx, folderInfos, err);
}
// open files of different mime-types with their default app
for(auto& typeFiles : mimeTypeToFiles) {
auto& mimeType = typeFiles.first;
auto& files = typeFiles.second;
GErrorPtr err;
GAppInfoPtr app{g_app_info_get_default_for_type(mimeType.c_str(), false), false};
if(!app) {
app = chooseApp(files, mimeType.c_str(), err);
}
if(app) {
launchWithApp(app.get(), files.paths(), ctx);
}
}
if(!pathsToLaunch.empty()) {
launchPaths(pathsToLaunch, ctx);
}
return true;
}
bool BasicFileLauncher::launchPaths(FilePathList paths, GAppLaunchContext* ctx) {
// FIXME: blocking with an event loop is not a good design :-(
QEventLoop eventLoop;
auto job = new FileInfoJob{paths};
job->setAutoDelete(false); // do not automatically delete the job since we want its results later.
GObjectPtr<GAppLaunchContext> ctxPtr{ctx};
QObject::connect(job, &FileInfoJob::finished,
[&eventLoop]() {
// exit the event loop when the job is done
eventLoop.exit();
});
// run the job in another thread to not block the UI
job->runAsync();
// blocking until the job is done with a event loop
eventLoop.exec();
// launch the file info
launchFiles(job->files(), ctx);
delete job;
return false;
}
GAppInfoPtr BasicFileLauncher::chooseApp(const FileInfoList& /* fileInfos */, const char* /*mimeType*/, GErrorPtr& /* err */) {
return GAppInfoPtr{};
}
bool BasicFileLauncher::openFolder(GAppLaunchContext* ctx, const FileInfoList& folderInfos, GErrorPtr& err) {
auto app = chooseApp(folderInfos, "inode/directory", err);
if(app) {
launchWithApp(app.get(), folderInfos.paths(), ctx);
}
else {
showError(ctx, err);
}
return false;
}
BasicFileLauncher::ExecAction BasicFileLauncher::askExecFile(const FileInfoPtr & /* file */) {
return ExecAction::DIRECT_EXEC;
}
bool BasicFileLauncher::showError(GAppLaunchContext* /* ctx */, GErrorPtr& /* err */, const FilePath& /* path */, const FileInfoPtr& /* info */) {
return false;
}
int BasicFileLauncher::ask(const char* /* msg */, char* const* /* btn_labels */, int default_btn) {
return default_btn;
}
bool BasicFileLauncher::launchWithApp(GAppInfo* app, const FilePathList& paths, GAppLaunchContext* ctx) {
GList* uris = nullptr;
for(auto& path : paths) {
auto uri = path.uri();
uris = g_list_prepend(uris, uri.release());
}
GErrorPtr err;
bool ret = bool(g_app_info_launch_uris(app, uris, ctx, &err));
g_list_foreach(uris, reinterpret_cast<GFunc>(g_free), nullptr);
g_list_free(uris);
if(!ret) {
// FIXME: show error for all files
showError(ctx, err, paths[0]);
}
return ret;
}
bool BasicFileLauncher::launchDesktopEntry(const FileInfoPtr &fileInfo, const FilePathList &paths, GAppLaunchContext* ctx) {
/* treat desktop entries as executables */
auto target = fileInfo->target();
CStrPtr filename;
const char* desktopEntryName = nullptr;
FilePathList shortcutTargetPaths;
if(fileInfo->isExecutableType()) {
auto act = quickExec_ ? ExecAction::DIRECT_EXEC : askExecFile(fileInfo);
switch(act) {
case ExecAction::EXEC_IN_TERMINAL:
case ExecAction::DIRECT_EXEC: {
if(fileInfo->isShortcut()) {
auto path = handleShortcut(fileInfo, ctx);
if(path.isValid()) {
shortcutTargetPaths.emplace_back(path);
}
}
else {
if(target.empty()) {
filename = fileInfo->path().localPath();
}
desktopEntryName = !target.empty() ? target.c_str() : filename.get();
}
break;
}
case ExecAction::OPEN_WITH_DEFAULT_APP:
return launchWithDefaultApp(fileInfo, ctx);
case ExecAction::CANCEL:
return false;
default:
return false;
}
}
/* make exception for desktop entries under menu */
else if(fileInfo->isNative() /* an exception */ ||
fileInfo->path().hasUriScheme("menu")) {
if(target.empty()) {
filename = fileInfo->path().localPath();
}
desktopEntryName = !target.empty() ? target.c_str() : filename.get();
}
if(desktopEntryName) {
return launchDesktopEntry(desktopEntryName, paths, ctx);
}
if(!shortcutTargetPaths.empty()) {
launchPaths(shortcutTargetPaths, ctx);
}
return false;
}
bool BasicFileLauncher::launchDesktopEntry(const char *desktopEntryName, const FilePathList &paths, GAppLaunchContext *ctx) {
bool ret = false;
GAppInfo* app;
/* Let GDesktopAppInfo try first. */
if(g_path_is_absolute(desktopEntryName)) {
app = G_APP_INFO(g_desktop_app_info_new_from_filename(desktopEntryName));
}
else {
app = G_APP_INFO(g_desktop_app_info_new(desktopEntryName));
}
/* we handle Type=Link in FmFileInfo so if GIO failed then
it cannot be launched in fact */
if(app) {
return launchWithApp(app, paths, ctx);
}
else {
QString msg = QObject::tr("Invalid desktop entry file: '%1'").arg(desktopEntryName);
GErrorPtr err{G_IO_ERROR, G_IO_ERROR_FAILED, msg};
showError(ctx, err);
}
return ret;
}
FilePath BasicFileLauncher::handleShortcut(const FileInfoPtr& fileInfo, GAppLaunchContext* ctx) {
auto target = fileInfo->target();
auto scheme = CStrPtr{g_uri_parse_scheme(target.c_str())};
if(scheme) {
// collect the uri schemes we support
if(strcmp(scheme.get(), "file") == 0
|| strcmp(scheme.get(), "trash") == 0
|| strcmp(scheme.get(), "network") == 0
|| strcmp(scheme.get(), "computer") == 0) {
return FilePath::fromUri(fileInfo->target().c_str());
}
else {
// ask gio to launch the default handler for the uri scheme
GAppInfoPtr app{g_app_info_get_default_for_uri_scheme(scheme.get()), false};
FilePathList uris{FilePath::fromUri(fileInfo->target().c_str())};
launchWithApp(app.get(), uris, ctx);
}
}
else {
// see it as a local path
return FilePath::fromLocalPath(fileInfo->target().c_str());
}
return FilePath();
}
bool BasicFileLauncher::launchExecutable(const FileInfoPtr &fileInfo, GAppLaunchContext* ctx) {
/* if it's an executable file, directly execute it. */
auto filename = fileInfo->path().localPath();
/* FIXME: we need to use eaccess/euidaccess here. */
if(g_file_test(filename.get(), G_FILE_TEST_IS_EXECUTABLE)) {
auto act = quickExec_ ? ExecAction::DIRECT_EXEC : askExecFile(fileInfo);
int flags = G_APP_INFO_CREATE_NONE;
switch(act) {
case ExecAction::EXEC_IN_TERMINAL:
flags |= G_APP_INFO_CREATE_NEEDS_TERMINAL;
/* Falls through. */
case ExecAction::DIRECT_EXEC: {
/* filename may contain spaces. Fix #3143296 */
CStrPtr quoted{g_shell_quote(filename.get())};
// FIXME: remove libfm dependency
GAppInfoPtr app{fm_app_info_create_from_commandline(quoted.get(), nullptr, GAppInfoCreateFlags(flags), nullptr)};
if(app) {
CStrPtr run_path{g_path_get_dirname(filename.get())};
CStrPtr cwd;
/* bug #3589641: scripts are ran from $HOME.
since GIO launcher is kinda ugly - it has
no means to set running directory so we
do workaround - change directory to it */
if(run_path && strcmp(run_path.get(), ".")) {
cwd = CStrPtr{g_get_current_dir()};
if(chdir(run_path.get()) != 0) {
cwd.reset();
// show errors
QString msg = QObject::tr("Cannot set working directory to '%1': %2").arg(run_path.get()).arg(g_strerror(errno));
GErrorPtr err{G_IO_ERROR, g_io_error_from_errno(errno), msg};
showError(ctx, err);
}
}
// FIXME: remove libfm dependency
GErrorPtr err;
if(!fm_app_info_launch(app.get(), nullptr, ctx, &err)) {
showError(ctx, err);
}
if(cwd) { /* return back */
if(chdir(cwd.get()) != 0) {
g_warning("fm_launch_files(): chdir() failed");
}
}
return true;
}
break;
}
case ExecAction::OPEN_WITH_DEFAULT_APP:
return launchWithDefaultApp(fileInfo, ctx);
case ExecAction::CANCEL:
default:
break;
}
}
return false;
}
bool BasicFileLauncher::launchWithDefaultApp(const FileInfoPtr &fileInfo, GAppLaunchContext* ctx) {
FileInfoList files;
files.emplace_back(fileInfo);
GErrorPtr err;
GAppInfoPtr app{g_app_info_get_default_for_type(fileInfo->mimeType()->name(), false), false};
if(app) {
return launchWithApp(app.get(), files.paths(), ctx);
}
else {
showError(ctx, err, fileInfo->path());
}
return false;
}
} // namespace Fm

@ -1,72 +0,0 @@
#ifndef BASICFILELAUNCHER_H
#define BASICFILELAUNCHER_H
#include "../libfmqtglobals.h"
#include "fileinfo.h"
#include "filepath.h"
#include "mimetype.h"
#include <gio/gio.h>
namespace Fm {
class LIBFM_QT_API BasicFileLauncher {
public:
enum class ExecAction {
NONE,
DIRECT_EXEC,
EXEC_IN_TERMINAL,
OPEN_WITH_DEFAULT_APP,
CANCEL
};
explicit BasicFileLauncher();
virtual ~BasicFileLauncher();
bool launchFiles(const FileInfoList &fileInfos, GAppLaunchContext* ctx = nullptr);
bool launchPaths(FilePathList paths, GAppLaunchContext* ctx = nullptr);
bool launchDesktopEntry(const FileInfoPtr &fileInfo, const FilePathList& paths = FilePathList{}, GAppLaunchContext* ctx = nullptr);
bool launchDesktopEntry(const char* desktopEntryName, const FilePathList& paths = FilePathList{}, GAppLaunchContext* ctx = nullptr);
bool launchWithDefaultApp(const FileInfoPtr& fileInfo, GAppLaunchContext* ctx = nullptr);
bool launchWithApp(GAppInfo* app, const FilePathList& paths, GAppLaunchContext* ctx = nullptr);
bool launchExecutable(const FileInfoPtr &fileInfo, GAppLaunchContext* ctx = nullptr);
bool quickExec() const {
return quickExec_;
}
void setQuickExec(bool value) {
quickExec_ = value;
}
protected:
virtual GAppInfoPtr chooseApp(const FileInfoList& fileInfos, const char* mimeType, GErrorPtr& err);
virtual bool openFolder(GAppLaunchContext* ctx, const FileInfoList& folderInfos, GErrorPtr& err);
virtual bool showError(GAppLaunchContext* ctx, GErrorPtr& err, const FilePath& path = FilePath{}, const FileInfoPtr& info = FileInfoPtr{});
virtual ExecAction askExecFile(const FileInfoPtr& file);
virtual int ask(const char* msg, char* const* btn_labels, int default_btn);
private:
FilePath handleShortcut(const FileInfoPtr &fileInfo, GAppLaunchContext* ctx = nullptr);
private:
bool quickExec_; // Don't ask options on launch executable file
};
} // namespace Fm
#endif // BASICFILELAUNCHER_H

@ -1,217 +0,0 @@
#include "bookmarks.h"
#include "cstrptr.h"
#include <algorithm>
#include <QTimer>
#include <QStandardPaths>
namespace Fm {
std::weak_ptr<Bookmarks> Bookmarks::globalInstance_;
static inline CStrPtr get_legacy_bookmarks_file(void) {
return CStrPtr{g_build_filename(g_get_home_dir(), ".gtk-bookmarks", nullptr)};
}
static inline CStrPtr get_new_bookmarks_file(void) {
return CStrPtr{g_build_filename(g_get_user_config_dir(), "gtk-3.0", "bookmarks", nullptr)};
}
BookmarkItem::BookmarkItem(const FilePath& path, const QString name):
path_{path},
name_{name} {
if(name_.isEmpty()) { // if the name is not specified, use basename of the path
name_ = path_.baseName().get();
}
// We cannot rely on FileInfos to set bookmark icons because there is no guarantee
// that FileInfos already exist, while their creation is costly. Therefore, we have
// to get folder icons directly, as is done at `FileInfo::setFromGFileInfo` and more.
auto local_path = path.localPath();
auto dot_dir = CStrPtr{g_build_filename(local_path.get(), ".directory", nullptr)};
if(g_file_test(dot_dir.get(), G_FILE_TEST_IS_REGULAR)) {
GKeyFile* kf = g_key_file_new();
if(g_key_file_load_from_file(kf, dot_dir.get(), G_KEY_FILE_NONE, nullptr)) {
CStrPtr icon_name{g_key_file_get_string(kf, "Desktop Entry", "Icon", nullptr)};
if(icon_name) {
icon_ = IconInfo::fromName(icon_name.get());
}
}
g_key_file_free(kf);
}
if(!icon_ || !icon_->isValid()) {
// first check some standard folders that are shared by Qt and GLib
if(path_ == FilePath::homeDir()) {
icon_ = IconInfo::fromName("user-home");
}
else if (path_.parent() == FilePath::homeDir()) {
QString folderPath = QString::fromUtf8(path_.toString().get());
if(folderPath == QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)) {
icon_ = IconInfo::fromName("user-desktop");
}
else if(folderPath == QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)) {
icon_ = IconInfo::fromName("folder-documents");
}
else if(folderPath == QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)) {
icon_ = IconInfo::fromName("folder-download");
}
else if(folderPath == QStandardPaths::writableLocation(QStandardPaths::MusicLocation)) {
icon_ = IconInfo::fromName("folder-music");
}
else if(folderPath == QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)) {
icon_ = IconInfo::fromName("folder-pictures");
}
else if(folderPath == QStandardPaths::writableLocation(QStandardPaths::MoviesLocation)) {
icon_ = IconInfo::fromName("folder-videos");
}
}
// fall back to the default folder icon
if(!icon_ || !icon_->isValid()) {
icon_ = IconInfo::fromName("folder");
}
}
}
Bookmarks::Bookmarks(QObject* parent):
QObject(parent),
idle_handler{false} {
/* trying the gtk-3.0 first and use it if it exists */
auto fpath = get_new_bookmarks_file();
file = FilePath::fromLocalPath(fpath.get());
load();
if(items_.empty()) { /* not found, use legacy file */
fpath = get_legacy_bookmarks_file();
file = FilePath::fromLocalPath(fpath.get());
load();
}
mon = GObjectPtr<GFileMonitor>{g_file_monitor_file(file.gfile().get(), G_FILE_MONITOR_NONE, nullptr, nullptr), false};
if(mon) {
g_signal_connect(mon.get(), "changed", G_CALLBACK(_onFileChanged), this);
}
}
Bookmarks::~Bookmarks() {
if(mon) {
g_signal_handlers_disconnect_by_data(mon.get(), this);
}
}
const std::shared_ptr<const BookmarkItem>& Bookmarks::insert(const FilePath& path, const QString& name, int pos) {
const auto insert_pos = (pos < 0 || static_cast<size_t>(pos) > items_.size()) ? items_.cend() : items_.cbegin() + pos;
auto it = items_.insert(insert_pos, std::make_shared<const BookmarkItem>(path, name));
queueSave();
return *it;
}
void Bookmarks::remove(const std::shared_ptr<const BookmarkItem>& item) {
items_.erase(std::remove(items_.begin(), items_.end(), item), items_.end());
queueSave();
}
void Bookmarks::reorder(const std::shared_ptr<const BookmarkItem>& item, int pos) {
auto old_it = std::find(items_.cbegin(), items_.cend(), item);
if(old_it == items_.cend())
return;
std::shared_ptr<const BookmarkItem> newItem = item;
auto old_pos = old_it - items_.cbegin();
items_.erase(old_it);
if(old_pos < pos)
--pos;
auto new_it = items_.cbegin() + pos;
if(new_it > items_.cend())
new_it = items_.cend();
items_.insert(new_it, std::move(newItem));
queueSave();
}
void Bookmarks::rename(const std::shared_ptr<const BookmarkItem>& item, QString new_name) {
auto it = std::find_if(items_.cbegin(), items_.cend(), [item](const std::shared_ptr<const BookmarkItem>& elem) {
return elem->path() == item->path();
});
if(it != items_.cend()) {
// create a new item to replace the old one
// we do not modify the old item directly since this data structure is shared with others
it = items_.insert(it, std::make_shared<const BookmarkItem>(item->path(), new_name));
items_.erase(it + 1); // remove the old item
queueSave();
}
}
std::shared_ptr<Bookmarks> Bookmarks::globalInstance() {
auto bookmarks = globalInstance_.lock();
if(!bookmarks) {
bookmarks = std::make_shared<Bookmarks>();
globalInstance_ = bookmarks;
}
return bookmarks;
}
void Bookmarks::save() {
std::string buf;
// G_LOCK(bookmarks);
for(auto& item: items_) {
auto uri = item->path().uri();
buf += uri.get();
buf += ' ';
buf += item->name().toUtf8().constData();
buf += '\n';
}
idle_handler = false;
// G_UNLOCK(bookmarks);
GError* err = nullptr;
if(!g_file_replace_contents(file.gfile().get(), buf.c_str(), buf.length(), nullptr,
FALSE, G_FILE_CREATE_NONE, nullptr, nullptr, &err)) {
g_critical("%s", err->message);
g_error_free(err);
}
/* we changed bookmarks list, let inform who interested in that */
Q_EMIT changed();
}
void Bookmarks::load() {
auto fpath = file.localPath();
FILE* f;
char buf[1024];
/* load the file */
f = fopen(fpath.get(), "r");
if(f) {
while(fgets(buf, 1024, f)) {
// format of each line in the bookmark file:
// <URI> <name>\n
char* sep;
sep = strchr(buf, '\n');
if(sep) {
*sep = '\0';
}
QString name;
sep = strchr(buf, ' '); // find the separator between URI and name
if(sep) {
*sep = '\0';
name = sep + 1;
}
auto uri = buf;
if(uri[0] != '\0') {
items_.push_back(std::make_shared<BookmarkItem>(FilePath::fromUri(uri), name));
}
}
fclose(f);
}
}
void Bookmarks::onFileChanged(GFileMonitor* /*mon*/, GFile* /*gf*/, GFile* /*other*/, GFileMonitorEvent /*evt*/) {
// reload the bookmarks
items_.clear();
load();
Q_EMIT changed();
}
void Bookmarks::queueSave() {
if(!idle_handler) {
QTimer::singleShot(0, this, &Bookmarks::save);
idle_handler = true;
}
}
} // namespace Fm

@ -1,87 +0,0 @@
#ifndef FM2_BOOKMARKS_H
#define FM2_BOOKMARKS_H
#include <QObject>
#include "gobjectptr.h"
#include "filepath.h"
#include "iconinfo.h"
namespace Fm {
class LIBFM_QT_API BookmarkItem {
public:
friend class Bookmarks;
explicit BookmarkItem(const FilePath& path, const QString name);
const QString& name() const {
return name_;
}
const FilePath& path() const {
return path_;
}
const std::shared_ptr<const IconInfo>& icon() const {
return icon_;
}
private:
void setName(const QString& name) {
name_ = name;
}
private:
FilePath path_;
QString name_;
std::shared_ptr<const IconInfo> icon_;
};
class LIBFM_QT_API Bookmarks : public QObject {
Q_OBJECT
public:
explicit Bookmarks(QObject* parent = 0);
~Bookmarks();
const std::shared_ptr<const BookmarkItem> &insert(const FilePath& path, const QString& name, int pos);
void remove(const std::shared_ptr<const BookmarkItem>& item);
void reorder(const std::shared_ptr<const BookmarkItem> &item, int pos);
void rename(const std::shared_ptr<const BookmarkItem>& item, QString new_name);
const std::vector<std::shared_ptr<const BookmarkItem>>& items() const {
return items_;
}
static std::shared_ptr<Bookmarks> globalInstance();
Q_SIGNALS:
void changed();
private Q_SLOTS:
void save();
private:
void load();
void queueSave();
static void _onFileChanged(GFileMonitor* mon, GFile* gf, GFile* other, GFileMonitorEvent evt, Bookmarks* _this) {
_this->onFileChanged(mon, gf, other, evt);
}
void onFileChanged(GFileMonitor* mon, GFile* gf, GFile* other, GFileMonitorEvent evt);
private:
FilePath file;
GObjectPtr<GFileMonitor> mon;
std::vector<std::shared_ptr<const BookmarkItem>> items_;
static std::weak_ptr<Bookmarks> globalInstance_;
bool idle_handler;
};
} // namespace Fm
#endif // FM2_BOOKMARKS_H

@ -1,42 +0,0 @@
#ifndef FM2_CSTRPTR_H
#define FM2_CSTRPTR_H
#include <memory>
#include <glib.h>
namespace Fm {
struct CStrDeleter {
void operator()(char* ptr) {
g_free(ptr);
}
};
// smart pointer for C string (char*) which should be freed by free()
typedef std::unique_ptr<char[], CStrDeleter> CStrPtr;
struct CStrHash {
std::size_t operator()(const char* str) const {
return g_str_hash(str);
}
};
struct CStrEqual {
bool operator()(const char* str1, const char* str2) const {
return g_str_equal(str1, str2);
}
};
struct CStrVDeleter {
void operator()(char** ptr) {
g_strfreev(ptr);
}
};
// smart pointer for C string array (char**) which should be freed by g_strfreev() of glib
typedef std::unique_ptr<char*[], CStrVDeleter> CStrArrayPtr;
} // namespace Fm
#endif // FM2_CSTRPTR_H

@ -1,158 +0,0 @@
#include "deletejob.h"
#include "totalsizejob.h"
#include "fileinfo_p.h"
namespace Fm {
bool DeleteJob::deleteFile(const FilePath& path, GFileInfoPtr inf) {
ErrorAction act = ErrorAction::CONTINUE;
while(!inf) {
GErrorPtr err;
inf = GFileInfoPtr{
g_file_query_info(path.gfile().get(), "standard::*",
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err),
false
};
if(err) {
act = emitError(err, ErrorSeverity::SEVERE);
if(act == ErrorAction::ABORT) {
return false;
}
if(act != ErrorAction::RETRY) {
break;
}
}
}
// TODO: get parent dir of the current path.
// if there is a Fm::Folder object created for it, block the update for the folder temporarily.
/* currently processed file. */
setCurrentFile(path);
if(g_file_info_get_file_type(inf.get()) == G_FILE_TYPE_DIRECTORY) {
// delete the content of the dir prior to deleting itself
deleteDirContent(path, inf);
}
bool isTrashRoot = false;
// special handling for trash:///
if(!path.isNative() && g_strcmp0(path.uriScheme().get(), "trash") == 0) {
// little trick: basename of trash root is /
auto basename = path.baseName();
if(basename && basename[0] == G_DIR_SEPARATOR) {
isTrashRoot = true;
}
}
bool hasError = false;
while(!isCancelled()) {
GErrorPtr err;
// try to delete the path directly (but don't delete if it's trash:///)
if(isTrashRoot || g_file_delete(path.gfile().get(), cancellable().get(), &err)) {
break;
}
if(err) {
// FIXME: error handling
/* if it's non-empty dir then descent into it then try again */
/* trash root gives G_IO_ERROR_PERMISSION_DENIED */
if(err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_NOT_EMPTY) {
deleteDirContent(path, inf);
}
else if(err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_PERMISSION_DENIED) {
/* special case for trash:/// */
/* FIXME: is there any better way to handle this? */
auto scheme = path.uriScheme();
if(g_strcmp0(scheme.get(), "trash") == 0) {
break;
}
}
act = emitError(err, ErrorSeverity::MODERATE);
if(act != ErrorAction::RETRY) {
hasError = true;
break;
}
}
}
addFinishedAmount(g_file_info_get_size(inf.get()), 1);
return !hasError;
}
bool DeleteJob::deleteDirContent(const FilePath& path, GFileInfoPtr inf) {
GErrorPtr err;
GFileEnumeratorPtr enu {
g_file_enumerate_children(path.gfile().get(), defaultGFileInfoQueryAttribs,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err),
false
};
if(!enu) {
emitError(err, ErrorSeverity::MODERATE);
return false;
}
bool hasError = false;
while(!isCancelled()) {
inf = GFileInfoPtr{
g_file_enumerator_next_file(enu.get(), cancellable().get(), &err),
false
};
if(inf) {
auto subPath = path.child(g_file_info_get_name(inf.get()));
if(!deleteFile(subPath, inf)) {
continue;
}
}
else {
if(err) {
emitError(err, ErrorSeverity::MODERATE);
/* ErrorAction::RETRY is not supported here */
hasError = true;
}
else { /* EOF */
}
break;
}
}
g_file_enumerator_close(enu.get(), nullptr, nullptr);
return !hasError;
}
DeleteJob::DeleteJob(const FilePathList &paths): paths_{paths} {
setCalcProgressUsingSize(false);
}
DeleteJob::DeleteJob(FilePathList &&paths): paths_{paths} {
setCalcProgressUsingSize(false);
}
DeleteJob::~DeleteJob() {
}
void DeleteJob::exec() {
/* prepare the job, count total work needed with FmDeepCountJob */
TotalSizeJob totalSizeJob{paths_, TotalSizeJob::Flags::PREPARE_DELETE};
connect(&totalSizeJob, &TotalSizeJob::error, this, &DeleteJob::error);
connect(this, &DeleteJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel);
totalSizeJob.run();
if(isCancelled()) {
return;
}
setTotalAmount(totalSizeJob.totalSize(), totalSizeJob.fileCount());
Q_EMIT preparedToRun();
for(auto& path : paths_) {
if(isCancelled()) {
break;
}
deleteFile(path, GFileInfoPtr{nullptr});
}
}
} // namespace Fm

@ -1,33 +0,0 @@
#ifndef FM2_DELETEJOB_H
#define FM2_DELETEJOB_H
#include "../libfmqtglobals.h"
#include "fileoperationjob.h"
#include "filepath.h"
#include "gioptrs.h"
namespace Fm {
class LIBFM_QT_API DeleteJob : public Fm::FileOperationJob {
Q_OBJECT
public:
explicit DeleteJob(const FilePathList& paths);
explicit DeleteJob(FilePathList&& paths);
~DeleteJob();
protected:
void exec() override;
private:
bool deleteFile(const FilePath& path, GFileInfoPtr inf);
bool deleteDirContent(const FilePath& path, GFileInfoPtr inf);
private:
FilePathList paths_;
};
} // namespace Fm
#endif // FM2_DELETEJOB_H

@ -1,178 +0,0 @@
#include "dirlistjob.h"
#include <gio/gio.h>
#include "fileinfo_p.h"
#include "gioptrs.h"
#include <QDebug>
namespace Fm {
DirListJob::DirListJob(const FilePath& path, Flags _flags, const std::shared_ptr<const HashSet>& cutFilesHashSet):
dir_path{path}, flags{_flags}, cutFilesHashSet_{cutFilesHashSet} {
}
void DirListJob::exec() {
GErrorPtr err;
GFileInfoPtr dir_inf;
GFilePtr dir_gfile = dir_path.gfile();
// FIXME: these are hacks for search:/// URI implemented by libfm which contains some bugs
bool isFileSearch = dir_path.hasUriScheme("search");
if(isFileSearch) {
// NOTE: The GFile instance changes its URI during file enumeration (bad design).
// So we create a copy here to avoid channging the gfile stored in dir_path.
// FIXME: later we should refactor file search and remove this dirty hack.
dir_gfile = GFilePtr{g_file_dup(dir_gfile.get())};
}
_retry:
err.reset();
dir_inf = GFileInfoPtr{
g_file_query_info(dir_gfile.get(), defaultGFileInfoQueryAttribs,
G_FILE_QUERY_INFO_NONE, cancellable().get(), &err),
false
};
if(!dir_inf) {
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
if(act == ErrorAction::RETRY) {
err.reset();
goto _retry;
}
return;
}
if(g_file_info_get_file_type(dir_inf.get()) != G_FILE_TYPE_DIRECTORY) {
auto path_str = dir_path.toString();
err = GErrorPtr{
G_IO_ERROR,
G_IO_ERROR_NOT_DIRECTORY,
tr("The specified directory '%1' is not valid").arg(path_str.get())
};
emitError(err, ErrorSeverity::CRITICAL);
return;
}
else {
std::lock_guard<std::mutex> lock{mutex_};
dir_fi = std::make_shared<FileInfo>(dir_inf, dir_path.parent());
}
FileInfoList foundFiles;
/* check if FS is R/O and set attr. into inf */
// FIXME: _fm_file_info_job_update_fs_readonly(gf, inf, nullptr, nullptr);
err.reset();
GFileEnumeratorPtr enu = GFileEnumeratorPtr{
g_file_enumerate_children(dir_gfile.get(), defaultGFileInfoQueryAttribs,
G_FILE_QUERY_INFO_NONE, cancellable().get(), &err),
false
};
if(enu) {
// qDebug() << "START LISTING:" << dir_path.toString().get();
while(!isCancelled()) {
err.reset();
GFileInfoPtr inf{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false};
if(inf) {
#if 0
FmPath* dir, *sub;
GFile* child;
if(G_UNLIKELY(job->flags & FM_DIR_LIST_JOB_DIR_ONLY)) {
/* FIXME: handle symlinks */
if(g_file_info_get_file_type(inf) != G_FILE_TYPE_DIRECTORY) {
g_object_unref(inf);
continue;
}
}
#endif
// virtual folders may return children not within them
// For example: the search:/// URI implemented by libfm might return files from different folders during enumeration.
// So here we call g_file_enumerator_get_container() to get the real parent path rather than simply using dir_path.
// This is not the behaviour of gio, but the extensions by libfm might do this.
// FIXME: after we port these vfs implementation from libfm, we can redesign this.
FilePath realParentPath = FilePath{g_file_enumerator_get_container(enu.get()), true};
if(isFileSearch) { // this is a file sarch job (search:/// URI)
// FIXME: redesign file search and remove this dirty hack
// the libfm implementation of search:/// URI returns a customized GFile implementation that does not behave normally.
// let's get its actual URI and re-create a normal gio GFile instance from it.
realParentPath = FilePath::fromUri(realParentPath.uri().get());
}
#if 0
if(g_file_info_get_file_type(inf) == G_FILE_TYPE_DIRECTORY)
/* for dir: check if its FS is R/O and set attr. into inf */
{
_fm_file_info_job_update_fs_readonly(child, inf, nullptr, nullptr);
}
fi = fm_file_info_new_from_g_file_data(child, inf, sub);
#endif
auto fileInfo = std::make_shared<FileInfo>(inf, realParentPath);
if(emit_files_found) {
// Q_EMIT filesFound();
}
if(cutFilesHashSet_
&& cutFilesHashSet_->count(fileInfo->path().hash()) > 0) {
fileInfo->bindCutFiles(cutFilesHashSet_);
}
foundFiles.push_back(std::move(fileInfo));
}
else {
if(err) {
ErrorAction act = emitError(err, ErrorSeverity::MILD);
/* ErrorAction::RETRY is not supported. */
if(act == ErrorAction::ABORT) {
cancel();
}
}
/* otherwise it's EOL */
break;
}
}
err.reset();
g_file_enumerator_close(enu.get(), cancellable().get(), &err);
}
else {
emitError(err, ErrorSeverity::CRITICAL);
}
// qDebug() << "END LISTING:" << dir_path.toString().get();
if(!foundFiles.empty()) {
std::lock_guard<std::mutex> lock{mutex_};
files_.swap(foundFiles);
}
}
#if 0
//FIXME: incremental..
static gboolean emit_found_files(gpointer user_data) {
/* this callback is called from the main thread */
FmDirListJob* job = FM_DIR_LIST_JOB(user_data);
/* g_print("emit_found_files: %d\n", g_slist_length(job->files_to_add)); */
if(g_source_is_destroyed(g_main_current_source())) {
return FALSE;
}
g_signal_emit(job, signals[FILES_FOUND], 0, job->files_to_add);
g_slist_free_full(job->files_to_add, (GDestroyNotify)fm_file_info_unref);
job->files_to_add = nullptr;
job->delay_add_files_handler = 0;
return FALSE;
}
static gpointer queue_add_file(FmJob* fmjob, gpointer user_data) {
FmDirListJob* job = FM_DIR_LIST_JOB(fmjob);
FmFileInfo* file = FM_FILE_INFO(user_data);
/* this callback is called from the main thread */
/* g_print("queue_add_file: %s\n", fm_file_info_get_disp_name(file)); */
job->files_to_add = g_slist_prepend(job->files_to_add, fm_file_info_ref(file));
if(job->delay_add_files_handler == 0)
job->delay_add_files_handler = g_timeout_add_seconds_full(G_PRIORITY_LOW,
1, emit_found_files, g_object_ref(job), g_object_unref);
return nullptr;
}
void fm_dir_list_job_add_found_file(FmDirListJob* job, FmFileInfo* file) {
fm_file_info_list_push_tail(job->files, file);
if(G_UNLIKELY(job->emit_files_found)) {
fm_job_call_main_thread(FM_JOB(job), queue_add_file, file);
}
}
#endif
} // namespace Fm

@ -1,65 +0,0 @@
#ifndef FM2_DIRLISTJOB_H
#define FM2_DIRLISTJOB_H
#include "../libfmqtglobals.h"
#include <mutex>
#include "job.h"
#include "filepath.h"
#include "gobjectptr.h"
#include "fileinfo.h"
namespace Fm {
class LIBFM_QT_API DirListJob : public Job {
Q_OBJECT
public:
enum Flags {
FAST = 0,
DIR_ONLY = 1 << 0,
DETAILED = 1 << 1
};
explicit DirListJob(const FilePath& path, Flags flags, const std::shared_ptr<const HashSet>& cutFilesHashSet = nullptr);
FileInfoList& files() {
return files_;
}
void setIncremental(bool set);
bool incremental() const {
return emit_files_found;
}
FilePath dirPath() const {
std::lock_guard<std::mutex> lock{mutex_};
return dir_path;
}
std::shared_ptr<const FileInfo> dirInfo() const {
std::lock_guard<std::mutex> lock{mutex_};
return dir_fi;
}
Q_SIGNALS:
void filesFound(FileInfoList& foundFiles);
protected:
void exec() override;
private:
mutable std::mutex mutex_;
FilePath dir_path;
Flags flags;
std::shared_ptr<const FileInfo> dir_fi;
FileInfoList files_;
const std::shared_ptr<const HashSet> cutFilesHashSet_;
bool emit_files_found;
// guint delay_add_files_handler;
// GSList* files_to_add;
};
} // namespace Fm
#endif // FM2_DIRLISTJOB_H

@ -1,324 +0,0 @@
#include "filechangeattrjob.h"
#include "totalsizejob.h"
#include <sys/stat.h>
namespace Fm {
static const char query[] = G_FILE_ATTRIBUTE_STANDARD_TYPE","
G_FILE_ATTRIBUTE_STANDARD_NAME","
G_FILE_ATTRIBUTE_UNIX_GID","
G_FILE_ATTRIBUTE_UNIX_UID","
G_FILE_ATTRIBUTE_UNIX_MODE","
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME;
FileChangeAttrJob::FileChangeAttrJob(FilePathList paths):
paths_{std::move(paths)},
recursive_{false},
// chmod
fileModeEnabled_{false},
newMode_{0},
newModeMask_{0},
// chown
ownerEnabled_{false},
uid_{0},
groupEnabled_{false},
gid_{0},
// Display name
displayNameEnabled_{false},
// icon
iconEnabled_{false},
// hidden
hiddenEnabled_{false},
hidden_{false},
// target uri
targetUriEnabled_{false} {
// the progress of chmod/chown is not related to file size
setCalcProgressUsingSize(false);
}
void FileChangeAttrJob::exec() {
// count total amount of the work
if(recursive_) {
TotalSizeJob totalSizeJob{paths_};
connect(&totalSizeJob, &TotalSizeJob::error, this, &FileChangeAttrJob::error);
connect(this, &FileChangeAttrJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel);
totalSizeJob.run();
std::uint64_t totalSize, totalCount;
totalSizeJob.totalAmount(totalSize, totalCount);
setTotalAmount(totalSize, totalCount);
}
else {
setTotalAmount(paths_.size(), paths_.size());
}
// ready to start
Q_EMIT preparedToRun();
// do the actual change attrs job
for(auto& path : paths_) {
if(isCancelled()) {
break;
}
GErrorPtr err;
GFileInfoPtr info{
g_file_query_info(path.gfile().get(), query,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err),
false
};
if(info) {
processFile(path, info);
}
else {
handleError(err, path, info);
}
}
}
bool FileChangeAttrJob::processFile(const FilePath& path, const GFileInfoPtr& info) {
setCurrentFile(path);
bool ret = true;
if(ownerEnabled_) {
changeFileOwner(path, info, uid_);
}
if(groupEnabled_) {
changeFileGroup(path, info, gid_);
}
if(fileModeEnabled_) {
changeFileMode(path, info, newMode_, newModeMask_);
}
/* change display name, icon, hidden, target */
if(displayNameEnabled_ && !displayName().empty()) {
changeFileDisplayName(path, info, displayName_.c_str());
}
if(iconEnabled_ && icon_) {
changeFileIcon(path, info, icon_);
}
if(hiddenEnabled_) {
changeFileHidden(path, info, hidden_);
}
if(targetUriEnabled_ && !targetUri_.empty()) {
changeFileTargetUri(path, info, targetUri_.c_str());
}
// FIXME: do not use size 1 here.
addFinishedAmount(1, 1);
// recursively apply to subfolders
auto type = g_file_info_get_file_type(info.get());
if(!isCancelled() && recursive_ && type == G_FILE_TYPE_DIRECTORY) {
bool retry;
do {
retry = false;
GErrorPtr err;
GFileEnumeratorPtr enu{
g_file_enumerate_children(path.gfile().get(), query,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err),
false
};
if(enu) {
while(!isCancelled()) {
err.reset();
GFileInfoPtr childInfo{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false};
if(childInfo) {
auto childPath = path.child(g_file_info_get_name(childInfo.get()));
ret = processFile(childPath, childInfo);
if(!ret) { /* _fm_file_ops_job_change_attr_file() failed */
break;
}
}
else {
if(err) {
handleError(err, path, info, ErrorSeverity::MILD);
retry = false;
/* FM_JOB_RETRY is not supported here */
}
else { /* EOF */
break;
}
}
}
g_file_enumerator_close(enu.get(), cancellable().get(), nullptr);
}
else {
retry = handleError(err, path, info);
}
} while(!isCancelled() && retry);
}
return ret;
}
bool FileChangeAttrJob::handleError(GErrorPtr &err, const FilePath &path, const GFileInfoPtr &info, ErrorSeverity severity) {
auto act = emitError(err, severity);
if (act == ErrorAction::RETRY) {
err.reset();
return true;
}
return false;
}
bool FileChangeAttrJob::changeFileOwner(const FilePath& path, const GFileInfoPtr& info, uid_t uid) {
/* change owner */
bool ret = false;
bool retry;
do {
GErrorPtr err;
if(!g_file_set_attribute_uint32(path.gfile().get(), G_FILE_ATTRIBUTE_UNIX_UID,
uid, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err)) {
retry = handleError(err, path, info, ErrorSeverity::MILD);
err.reset();
}
else {
ret = true;
retry = false;
}
} while(retry && !isCancelled());
return ret;
}
bool FileChangeAttrJob::changeFileGroup(const FilePath& path, const GFileInfoPtr& info, gid_t gid) {
/* change group */
bool ret = false;
bool retry;
do {
GErrorPtr err;
if(!g_file_set_attribute_uint32(path.gfile().get(), G_FILE_ATTRIBUTE_UNIX_GID,
gid, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err)) {
retry = handleError(err, path, info, ErrorSeverity::MILD);
err.reset();
}
else {
ret = true;
retry = false;
}
} while(retry && !isCancelled());
return ret;
}
bool FileChangeAttrJob::changeFileMode(const FilePath& path, const GFileInfoPtr& info, mode_t newMode, mode_t newModeMask) {
bool ret = false;
/* change mode */
if(newModeMask) {
guint32 mode = g_file_info_get_attribute_uint32(info.get(), G_FILE_ATTRIBUTE_UNIX_MODE);
mode &= ~newModeMask;
mode |= (newMode & newModeMask);
auto type = g_file_info_get_file_type(info.get());
/* FIXME: this behavior should be optional. */
/* treat dirs with 'r' as 'rx' */
if(type == G_FILE_TYPE_DIRECTORY) {
if((newModeMask & S_IRUSR) && (mode & S_IRUSR)) {
mode |= S_IXUSR;
}
if((newModeMask & S_IRGRP) && (mode & S_IRGRP)) {
mode |= S_IXGRP;
}
if((newModeMask & S_IROTH) && (mode & S_IROTH)) {
mode |= S_IXOTH;
}
}
/* new mode */
bool retry;
do {
GErrorPtr err;
if(!g_file_set_attribute_uint32(path.gfile().get(), G_FILE_ATTRIBUTE_UNIX_MODE,
mode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err)) {
retry = handleError(err, path, info, ErrorSeverity::MILD);
err.reset();
}
else {
ret = true;
retry = false;
}
} while(retry && !isCancelled());
}
return ret;
}
bool FileChangeAttrJob::changeFileDisplayName(const FilePath& path, const GFileInfoPtr& info, const char* displayName) {
bool ret = false;
bool retry;
do {
GErrorPtr err;
if(!g_file_set_display_name(path.gfile().get(), displayName, cancellable().get(), &err)) {
retry = handleError(err, path, info, ErrorSeverity::MILD);
err.reset();
}
else {
ret = true;
retry = false;
}
} while(retry && !isCancelled());
return ret;
}
bool FileChangeAttrJob::changeFileIcon(const FilePath& path, const GFileInfoPtr& info, GIconPtr& icon) {
bool ret = false;
bool retry;
do {
GErrorPtr err;
if(!g_file_set_attribute(path.gfile().get(), G_FILE_ATTRIBUTE_STANDARD_ICON,
G_FILE_ATTRIBUTE_TYPE_OBJECT, icon.get(),
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err)) {
retry = handleError(err, path, info, ErrorSeverity::MILD);
err.reset();
}
else {
ret = true;
retry = false;
}
} while(retry && !isCancelled());
return ret;
}
bool FileChangeAttrJob::changeFileHidden(const FilePath& path, const GFileInfoPtr& info, bool hidden) {
bool ret = false;
bool retry;
do {
GErrorPtr err;
gboolean g_hidden = hidden;
if(!g_file_set_attribute(path.gfile().get(), G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
G_FILE_ATTRIBUTE_TYPE_BOOLEAN, &g_hidden,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err)) {
retry = handleError(err, path, info, ErrorSeverity::MILD);
err.reset();
}
else {
ret = true;
retry = false;
}
} while(retry && !isCancelled());
return ret;
}
bool FileChangeAttrJob::changeFileTargetUri(const FilePath& path, const GFileInfoPtr& info, const char* targetUri) {
bool ret = false;
bool retry;
do {
GErrorPtr err;
if(!g_file_set_attribute_string(path.gfile().get(), G_FILE_ATTRIBUTE_STANDARD_TARGET_URI,
targetUri, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err)) {
retry = handleError(err, path, info, ErrorSeverity::MILD);
err.reset();
}
else {
ret = true;
retry = false;
}
} while(retry && !isCancelled());
return ret;
}
} // namespace Fm

@ -1,145 +0,0 @@
#ifndef FM2_FILECHANGEATTRJOB_H
#define FM2_FILECHANGEATTRJOB_H
#include "../libfmqtglobals.h"
#include "fileoperationjob.h"
#include "gioptrs.h"
#include <string>
#include <sys/types.h>
namespace Fm {
class LIBFM_QT_API FileChangeAttrJob : public Fm::FileOperationJob {
Q_OBJECT
public:
explicit FileChangeAttrJob(FilePathList paths);
void setFileModeEnabled(bool enabled) {
fileModeEnabled_ = enabled;
}
void setFileMode(mode_t newMode, mode_t newModeMask) {
newMode_ = newMode;
newModeMask_ = newModeMask;
}
bool ownerEnabled() const {
return ownerEnabled_;
}
void setOwnerEnabled(bool enabled) {
ownerEnabled_ = enabled;
}
void setOwner(uid_t uid) {
uid_ = uid;
}
bool groupEnabled() const {
return groupEnabled_;
}
void setGroupEnabled(bool groupEnabled) {
groupEnabled_ = groupEnabled;
}
void setGroup(gid_t gid) {
gid_ = gid;
}
// This only work for change attr jobs.
void setRecursive(bool recursive) {
recursive_ = recursive;
}
void setHiddenEnabled(bool enabled) {
hiddenEnabled_ = enabled;
}
void setHidden(bool hidden) {
hidden_ = hidden;
}
bool iconEnabled() const {
return iconEnabled_;
}
void setIconEnabled(bool iconEnabled) {
iconEnabled_ = iconEnabled;
}
bool displayNameEnabled() const {
return displayNameEnabled_;
}
void setDisplayNameEnabled(bool displayNameEnabled) {
displayNameEnabled_ = displayNameEnabled;
}
const std::string& displayName() const {
return displayName_;
}
void setDisplayName(const std::string& displayName) {
displayName_ = displayName;
}
bool targetUriEnabled() const {
return targetUriEnabled_;
}
void setTargetUriEnabled(bool targetUriEnabled) {
targetUriEnabled_ = targetUriEnabled;
}
const std::string& targetUri() const {
return targetUri_;
}
void setTargetUri(const std::string& value) {
targetUri_ = value;
}
protected:
void exec() override;
private:
bool processFile(const FilePath& path, const GFileInfoPtr& info);
bool handleError(GErrorPtr& err, const FilePath& path, const GFileInfoPtr& info, ErrorSeverity severity = ErrorSeverity::MODERATE);
bool changeFileOwner(const FilePath& path, const GFileInfoPtr& info, uid_t uid);
bool changeFileGroup(const FilePath& path, const GFileInfoPtr& info, gid_t gid);
bool changeFileMode(const FilePath& path, const GFileInfoPtr& info, mode_t newMode, mode_t newModeMask);
bool changeFileDisplayName(const FilePath& path, const GFileInfoPtr& info, const char* displayName);
bool changeFileIcon(const FilePath& path, const GFileInfoPtr& info, GIconPtr& icon);
bool changeFileHidden(const FilePath& path, const GFileInfoPtr& info, bool hidden);
bool changeFileTargetUri(const FilePath& path, const GFileInfoPtr& info, const char* targetUri_);
private:
FilePathList paths_;
bool recursive_;
// chmod
bool fileModeEnabled_;
mode_t newMode_;
mode_t newModeMask_;
// chown
bool ownerEnabled_;
uid_t uid_;
bool groupEnabled_;
gid_t gid_;
// Display name
bool displayNameEnabled_;
std::string displayName_;
// icon
bool iconEnabled_;
GIconPtr icon_;
// hidden
bool hiddenEnabled_;
bool hidden_;
// target uri
bool targetUriEnabled_;
std::string targetUri_;
};
} // namespace Fm
#endif // FM2_FILECHANGEATTRJOB_H

@ -1,409 +0,0 @@
#include "fileinfo.h"
#include "fileinfo_p.h"
#include <gio/gio.h>
namespace Fm {
const char defaultGFileInfoQueryAttribs[] = "standard::*,"
"unix::*,"
"time::*,"
"access::*,"
"id::filesystem,"
"metadata::emblems";
FileInfo::FileInfo() {
// FIXME: initialize numeric data members
}
FileInfo::FileInfo(const GFileInfoPtr& inf, const FilePath& parentDirPath) {
setFromGFileInfo(inf, parentDirPath);
}
FileInfo::~FileInfo() {
}
void FileInfo::setFromGFileInfo(const GObjectPtr<GFileInfo>& inf, const FilePath& parentDirPath) {
dirPath_ = parentDirPath;
const char* tmp, *uri;
GIcon* gicon;
GFileType type;
if (const char * name = g_file_info_get_name(inf.get()))
name_ = name;
dispName_ = g_file_info_get_display_name(inf.get());
size_ = g_file_info_get_size(inf.get());
tmp = g_file_info_get_content_type(inf.get());
if(!tmp) {
tmp = "application/octet-stream";
}
mimeType_ = MimeType::fromName(tmp);
mode_ = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_UNIX_MODE);
uid_ = gid_ = -1;
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_UNIX_UID)) {
uid_ = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_UNIX_UID);
}
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_UNIX_GID)) {
gid_ = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_UNIX_GID);
}
type = g_file_info_get_file_type(inf.get());
if(0 == mode_) { /* if UNIX file mode is not available, compose a fake one. */
switch(type) {
case G_FILE_TYPE_REGULAR:
mode_ |= S_IFREG;
break;
case G_FILE_TYPE_DIRECTORY:
mode_ |= S_IFDIR;
break;
case G_FILE_TYPE_SYMBOLIC_LINK:
mode_ |= S_IFLNK;
break;
case G_FILE_TYPE_SHORTCUT:
break;
case G_FILE_TYPE_MOUNTABLE:
break;
case G_FILE_TYPE_SPECIAL:
if(mode_) {
break;
}
/* if it's a special file but it doesn't have UNIX mode, compose a fake one. */
if(strcmp(tmp, "inode/chardevice") == 0) {
mode_ |= S_IFCHR;
}
else if(strcmp(tmp, "inode/blockdevice") == 0) {
mode_ |= S_IFBLK;
}
else if(strcmp(tmp, "inode/fifo") == 0) {
mode_ |= S_IFIFO;
}
#ifdef S_IFSOCK
else if(strcmp(tmp, "inode/socket") == 0) {
mode_ |= S_IFSOCK;
}
#endif
break;
case G_FILE_TYPE_UNKNOWN:
;
}
}
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) {
isAccessible_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
}
else
/* assume it's accessible */
{
isAccessible_ = true;
}
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) {
isWritable_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
}
else
/* assume it's writable */
{
isWritable_ = true;
}
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE)) {
isDeletable_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE);
}
else
/* assume it's deletable */
{
isDeletable_ = true;
}
isShortcut_ = false;
/* special handling for symlinks */
if(g_file_info_get_is_symlink(inf.get())) {
mode_ &= ~S_IFMT; /* reset type */
mode_ |= S_IFLNK; /* set type to symlink */
goto _file_is_symlink;
}
switch(type) {
case G_FILE_TYPE_SHORTCUT:
isShortcut_ = true;
/* Falls through. */
case G_FILE_TYPE_MOUNTABLE:
uri = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
if(uri) {
if(g_str_has_prefix(uri, "file:///")) {
auto filename = CStrPtr{g_filename_from_uri(uri, nullptr, nullptr)};
target_ = filename.get();
}
else {
target_ = uri;
}
if(!mimeType_) {
mimeType_ = MimeType::guessFromFileName(target_.c_str());
}
}
/* if the mime-type is not determined or is unknown */
if(G_UNLIKELY(!mimeType_ || mimeType_->isUnknownType())) {
/* FIXME: is this appropriate? */
if(type == G_FILE_TYPE_SHORTCUT) {
mimeType_ = MimeType::inodeShortcut();
}
else {
mimeType_ = MimeType::inodeMountPoint();
}
}
break;
case G_FILE_TYPE_DIRECTORY:
if(!mimeType_) {
mimeType_ = MimeType::inodeDirectory();
}
isReadOnly_ = false; /* default is R/W */
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)) {
isReadOnly_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_READONLY);
}
/* directories should be writable to be deleted by user */
if(isReadOnly_ || !isWritable_) {
isDeletable_ = false;
}
break;
case G_FILE_TYPE_SYMBOLIC_LINK:
_file_is_symlink:
uri = g_file_info_get_symlink_target(inf.get());
if(uri) {
if(g_str_has_prefix(uri, "file:///")) {
auto filename = CStrPtr{g_filename_from_uri(uri, nullptr, nullptr)};
target_ = filename.get();
}
else {
target_ = uri;
}
if(!mimeType_) {
mimeType_ = MimeType::guessFromFileName(target_.c_str());
}
}
/* Falls through. */
/* continue with absent mime type */
default: /* G_FILE_TYPE_UNKNOWN G_FILE_TYPE_REGULAR G_FILE_TYPE_SPECIAL */
if(G_UNLIKELY(!mimeType_)) {
if(!mimeType_) {
mimeType_ = MimeType::guessFromFileName(name_.c_str());
}
}
}
/* if there is a custom folder icon, use it */
if(isNative() && type == G_FILE_TYPE_DIRECTORY) {
auto local_path = path().localPath();
auto dot_dir = CStrPtr{g_build_filename(local_path.get(), ".directory", nullptr)};
if(g_file_test(dot_dir.get(), G_FILE_TEST_IS_REGULAR)) {
GKeyFile* kf = g_key_file_new();
if(g_key_file_load_from_file(kf, dot_dir.get(), G_KEY_FILE_NONE, nullptr)) {
CStrPtr icon_name{g_key_file_get_string(kf, "Desktop Entry", "Icon", nullptr)};
if(icon_name) {
auto dot_icon = IconInfo::fromName(icon_name.get());
if(dot_icon && dot_icon->isValid()) {
icon_ = dot_icon;
}
}
}
g_key_file_free(kf);
}
}
if(!icon_) {
/* try file-specific icon first */
gicon = g_file_info_get_icon(inf.get());
if(gicon) {
icon_ = IconInfo::fromGIcon(gicon);
}
}
#if 0
/* set "locked" icon on unaccesible folder */
else if(!accessible && type == G_FILE_TYPE_DIRECTORY) {
icon = g_object_ref(icon_locked_folder);
}
else {
icon = g_object_ref(fm_mime_type_get_icon(mime_type));
}
#endif
/* if the file has emblems, add them to the icon */
auto emblem_names = g_file_info_get_attribute_stringv(inf.get(), "metadata::emblems");
if(emblem_names) {
auto n_emblems = g_strv_length(emblem_names);
for(int i = n_emblems - 1; i >= 0; --i) {
emblems_.emplace_front(Fm::IconInfo::fromName(emblem_names[i]));
}
}
tmp = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_ID_FILESYSTEM);
filesystemId_ = g_intern_string(tmp);
mtime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_MODIFIED);
atime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_ACCESS);
ctime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_CHANGED);
isHidden_ = g_file_info_get_is_hidden(inf.get());
// g_file_info_get_is_backup() does not cover ".bak" and ".old".
// NOTE: Here, dispName_ is not modified for desktop entries yet.
isBackup_ = g_file_info_get_is_backup(inf.get())
|| dispName_.endsWith(QLatin1String(".bak"))
|| dispName_.endsWith(QLatin1String(".old"));
isNameChangeable_ = true; /* GVFS tends to ignore this attribute */
isIconChangeable_ = isHiddenChangeable_ = false;
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME)) {
isNameChangeable_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME);
}
// special handling for desktop entry files (show the name and icon defined in the desktop entry instead)
if(isNative() && G_UNLIKELY(isDesktopEntry())) {
auto local_path = path().localPath();
GKeyFile* kf = g_key_file_new();
if(g_key_file_load_from_file(kf, local_path.get(), G_KEY_FILE_NONE, nullptr)) {
/* check if type is correct and supported */
CStrPtr type{g_key_file_get_string(kf, "Desktop Entry", "Type", nullptr)};
if(type) {
// Type == "Link"
if(strcmp(type.get(), G_KEY_FILE_DESKTOP_TYPE_LINK) == 0) {
CStrPtr uri{g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_URL, nullptr)};
if(uri) {
isShortcut_ = true;
target_ = uri.get();
}
}
}
CStrPtr icon_name{g_key_file_get_string(kf, "Desktop Entry", "Icon", nullptr)};
if(icon_name) {
icon_ = IconInfo::fromName(icon_name.get());
}
/* Use title of the desktop entry for display */
CStrPtr displayName{g_key_file_get_locale_string(kf, "Desktop Entry", "Name", nullptr, nullptr)};
if(displayName) {
dispName_ = displayName.get();
}
/* handle 'Hidden' key to set hidden attribute */
if(!isHidden_) {
isHidden_ = g_key_file_get_boolean(kf, "Desktop Entry", "Hidden", nullptr);
}
}
g_key_file_free(kf);
}
if(!icon_ && mimeType_)
icon_ = mimeType_->icon();
#if 0
GFile* _gf = nullptr;
GFileAttributeInfoList* list;
auto list = g_file_query_settable_attributes(gf, nullptr, nullptr);
if(G_LIKELY(list)) {
if(g_file_attribute_info_list_lookup(list, G_FILE_ATTRIBUTE_STANDARD_ICON)) {
icon_is_changeable = true;
}
if(g_file_attribute_info_list_lookup(list, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN)) {
hidden_is_changeable = true;
}
g_file_attribute_info_list_unref(list);
}
if(G_UNLIKELY(_gf)) {
g_object_unref(_gf);
}
#endif
}
void FileInfo::bindCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet) {
cutFilesHashSet_ = cutFilesHashSet;
}
bool FileInfo::canThumbnail() const {
/* We cannot use S_ISREG here as this exclude all symlinks */
if(size_ == 0 || /* don't generate thumbnails for empty files */
!(mode_ & S_IFREG) ||
isDesktopEntry() ||
isUnknownType()) {
return false;
}
return true;
}
/* full path of the file is required by this function */
bool FileInfo::isExecutableType() const {
if(isDesktopEntry()) {
/* treat desktop entries as executables if
they are native and have read permission */
if(isNative() && (mode_ & (S_IRUSR|S_IRGRP|S_IROTH))) {
if(isShortcut() && !target_.empty()) {
/* handle shortcuts from desktop to menu entries:
first check for entries in /usr/share/applications and such
which may be considered as a safe desktop entry path
then check if that is a shortcut to a native file
otherwise it is a link to a file under menu:// */
if (!g_str_has_prefix(target_.c_str(), "/usr/share/")) {
auto target = FilePath::fromPathStr(target_.c_str());
bool is_native = target.isNative();
if (is_native) {
return true;
}
}
}
else {
return true;
}
}
return false;
}
else if(isText()) { /* g_content_type_can_be_executable reports text files as executables too */
/* We don't execute remote files nor files in trash */
if(isNative() && (mode_ & (S_IXOTH | S_IXGRP | S_IXUSR))) {
/* it has executable bits so lets check shell-bang */
auto pathStr = path().toString();
int fd = open(pathStr.get(), O_RDONLY);
if(fd >= 0) {
char buf[2];
ssize_t rdlen = read(fd, &buf, 2);
close(fd);
if(rdlen == 2 && buf[0] == '#' && buf[1] == '!') {
return true;
}
}
}
return false;
}
return mimeType_->canBeExecutable();
}
bool FileInfoList::isSameType() const {
if(!empty()) {
auto& item = front();
for(auto it = cbegin() + 1; it != cend(); ++it) {
auto& item2 = *it;
if(item->mimeType() != item2->mimeType()) {
return false;
}
}
}
return true;
}
bool FileInfoList::isSameFilesystem() const {
if(!empty()) {
auto& item = front();
for(auto it = cbegin() + 1; it != cend(); ++it) {
auto& item2 = *it;
if(item->filesystemId() != item2->filesystemId()) {
return false;
}
}
}
return true;
}
} // namespace Fm

@ -1,283 +0,0 @@
/*
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef __LIBFM_QT_FM2_FILE_INFO_H__
#define __LIBFM_QT_FM2_FILE_INFO_H__
#include <libfm/fm.h>
#include <QObject>
#include <QtGlobal>
#include "../libfmqtglobals.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <vector>
#include <set>
#include <utility>
#include <string>
#include <forward_list>
#include "gioptrs.h"
#include "filepath.h"
#include "iconinfo.h"
#include "mimetype.h"
namespace Fm {
class FileInfoList;
typedef std::set<unsigned int> HashSet;
class LIBFM_QT_API FileInfo {
public:
explicit FileInfo();
explicit FileInfo(const GFileInfoPtr& inf, const FilePath& parentDirPath);
virtual ~FileInfo();
bool canSetHidden() const {
return isHiddenChangeable_;
}
bool canSetIcon() const {
return isIconChangeable_;
}
bool canSetName() const {
return isNameChangeable_;
}
bool canThumbnail() const;
gid_t gid() const {
return gid_;
}
uid_t uid() const {
return uid_;
}
const char* filesystemId() const {
return filesystemId_;
}
const std::shared_ptr<const IconInfo>& icon() const {
return icon_;
}
const std::shared_ptr<const MimeType>& mimeType() const {
return mimeType_;
}
quint64 ctime() const {
return ctime_;
}
quint64 atime() const {
return atime_;
}
quint64 mtime() const {
return mtime_;
}
const std::string& target() const {
return target_;
}
bool isWritableDirectory() const {
return (!isReadOnly_ && isDir());
}
bool isAccessible() const {
return isAccessible_;
}
bool isWritable() const {
return isWritable_;
}
bool isDeletable() const {
return isDeletable_;
}
bool isExecutableType() const;
bool isBackup() const {
return isBackup_;
}
bool isHidden() const {
// FIXME: we might treat backup files as hidden
return isHidden_;
}
bool isUnknownType() const {
return mimeType_->isUnknownType();
}
bool isDesktopEntry() const {
return mimeType_->isDesktopEntry();
}
bool isText() const {
return mimeType_->isText();
}
bool isImage() const {
return mimeType_->isImage();
}
bool isMountable() const {
return mimeType_->isMountable();
}
bool isShortcut() const {
return isShortcut_;
}
bool isSymlink() const {
return S_ISLNK(mode_) ? true : false;
}
bool isDir() const {
return S_ISDIR(mode_) || mimeType_->isDir();
}
bool isNative() const {
return dirPath_ ? dirPath_.isNative() : path().isNative();
}
bool isCut() const {
return !cutFilesHashSet_.expired();
}
mode_t mode() const {
return mode_;
}
uint64_t realSize() const {
return blksize_ *blocks_;
}
uint64_t size() const {
return size_;
}
const std::string& name() const {
return name_;
}
const QString& displayName() const {
return dispName_;
}
QString description() const {
return QString::fromUtf8(mimeType_ ? mimeType_->desc() : "");
}
FilePath path() const {
return dirPath_ ? dirPath_.child(name_.c_str()) : FilePath::fromPathStr(name_.c_str());
}
const FilePath& dirPath() const {
return dirPath_;
}
void setFromGFileInfo(const GFileInfoPtr& inf, const FilePath& parentDirPath);
void bindCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet);
const std::forward_list<std::shared_ptr<const IconInfo>>& emblems() const {
return emblems_;
}
private:
std::string name_;
QString dispName_;
FilePath dirPath_;
mode_t mode_;
const char* filesystemId_;
uid_t uid_;
gid_t gid_;
uint64_t size_;
quint64 mtime_;
quint64 atime_;
quint64 ctime_;
uint64_t blksize_;
uint64_t blocks_;
std::shared_ptr<const MimeType> mimeType_;
std::shared_ptr<const IconInfo> icon_;
std::forward_list<std::shared_ptr<const IconInfo>> emblems_;
std::string target_; /* target of shortcut or mountable. */
bool isShortcut_ : 1; /* TRUE if file is shortcut type */
bool isAccessible_ : 1; /* TRUE if can be read by user */
bool isWritable_ : 1; /* TRUE if can be written to by user */
bool isDeletable_ : 1; /* TRUE if can be deleted by user */
bool isHidden_ : 1; /* TRUE if file is hidden */
bool isBackup_ : 1; /* TRUE if file is backup */
bool isNameChangeable_ : 1; /* TRUE if name can be changed */
bool isIconChangeable_ : 1; /* TRUE if icon can be changed */
bool isHiddenChangeable_ : 1; /* TRUE if hidden can be changed */
bool isReadOnly_ : 1; /* TRUE if host FS is R/O */
std::weak_ptr<const HashSet> cutFilesHashSet_;
// std::vector<std::tuple<int, void*, void(void*)>> extraData_;
};
class LIBFM_QT_API FileInfoList: public std::vector<std::shared_ptr<const FileInfo>> {
public:
bool isSameType() const;
bool isSameFilesystem() const;
FilePathList paths() const {
FilePathList ret;
for(auto& file: *this) {
ret.push_back(file->path());
}
return ret;
}
};
// smart pointer to FileInfo objects (once created, FileInfo objects should stay immutable so const is needed here)
typedef std::shared_ptr<const FileInfo> FileInfoPtr;
typedef std::pair<FileInfoPtr, FileInfoPtr> FileInfoPair;
}
Q_DECLARE_METATYPE(std::shared_ptr<const Fm::FileInfo>)
#endif // __LIBFM_QT_FM2_FILE_INFO_H__

@ -1,10 +0,0 @@
#ifndef FILEINFO_P_H
#define FILEINFO_P_H
namespace Fm {
extern const char defaultGFileInfoQueryAttribs[];
} // namespace Fm
#endif // FILEINFO_P_H

@ -1,44 +0,0 @@
#include "fileinfojob.h"
#include "fileinfo_p.h"
namespace Fm {
FileInfoJob::FileInfoJob(FilePathList paths, FilePathList deletionPaths, FilePath commonDirPath, const std::shared_ptr<const HashSet>& cutFilesHashSet):
Job(),
paths_{std::move(paths)},
deletionPaths_{std::move(deletionPaths)},
commonDirPath_{std::move(commonDirPath)},
cutFilesHashSet_{cutFilesHashSet} {
}
void FileInfoJob::exec() {
for(const auto& path: paths_) {
if(!isCancelled()) {
GErrorPtr err;
GFileInfoPtr inf{
g_file_query_info(path.gfile().get(), defaultGFileInfoQueryAttribs,
G_FILE_QUERY_INFO_NONE, cancellable().get(), &err),
false
};
if(!inf) {
continue;
}
// Reuse the same dirPath object when the path remains the same (optimize for files in the same dir)
auto dirPath = commonDirPath_.isValid() ? commonDirPath_ : path.parent();
FileInfo fileInfo(inf, dirPath);
if(cutFilesHashSet_
&& cutFilesHashSet_->count(fileInfo.path().hash())) {
fileInfo.bindCutFiles(cutFilesHashSet_);
}
auto fileInfoPtr = std::make_shared<const FileInfo>(fileInfo);
results_.push_back(fileInfoPtr);
Q_EMIT gotInfo(path, fileInfoPtr);
}
}
}
} // namespace Fm

@ -1,46 +0,0 @@
#ifndef FM2_FILEINFOJOB_H
#define FM2_FILEINFOJOB_H
#include "../libfmqtglobals.h"
#include "job.h"
#include "filepath.h"
#include "fileinfo.h"
namespace Fm {
class LIBFM_QT_API FileInfoJob : public Job {
Q_OBJECT
public:
explicit FileInfoJob(FilePathList paths, FilePathList deletionPaths = FilePathList(), FilePath commonDirPath = FilePath(), const std::shared_ptr<const HashSet>& cutFilesHashSet = nullptr);
const FilePathList& paths() const {
return paths_;
}
const FilePathList& deletionPaths() const {
return deletionPaths_;
}
const FileInfoList& files() const {
return results_;
}
Q_SIGNALS:
void gotInfo(const FilePath& path, std::shared_ptr<const FileInfo>& info);
protected:
void exec() override;
private:
FilePathList paths_;
FilePathList deletionPaths_;
FileInfoList results_;
FilePath commonDirPath_;
const std::shared_ptr<const HashSet> cutFilesHashSet_;
};
} // namespace Fm
#endif // FM2_FILEINFOJOB_H

@ -1,9 +0,0 @@
#include "filelinkjob.h"
namespace Fm {
FileLinkJob::FileLinkJob() {
}
} // namespace Fm

@ -1,16 +0,0 @@
#ifndef FM2_FILELINKJOB_H
#define FM2_FILELINKJOB_H
#include "../libfmqtglobals.h"
#include "fileoperationjob.h"
namespace Fm {
class LIBFM_QT_API FileLinkJob : public Fm::FileOperationJob {
public:
explicit FileLinkJob();
};
} // namespace Fm
#endif // FM2_FILELINKJOB_H

@ -1,9 +0,0 @@
#include "filemonitor.h"
namespace Fm {
FileMonitor::FileMonitor() {
}
} // namespace Fm

@ -1,26 +0,0 @@
#ifndef FM2_FILEMONITOR_H
#define FM2_FILEMONITOR_H
#include "../libfmqtglobals.h"
#include <QObject>
#include "gioptrs.h"
#include "filepath.h"
namespace Fm {
class LIBFM_QT_API FileMonitor: public QObject {
Q_OBJECT
public:
explicit FileMonitor();
Q_SIGNALS:
private:
GFileMonitorPtr monitor_;
};
} // namespace Fm
#endif // FM2_FILEMONITOR_H

@ -1,102 +0,0 @@
#include "fileoperationjob.h"
namespace Fm {
FileOperationJob::FileOperationJob():
hasTotalAmount_{false},
calcProgressUsingSize_{true},
totalSize_{0},
totalCount_{0},
finishedSize_{0},
finishedCount_{0},
currentFileSize_{0},
currentFileFinished_{0} {
}
bool FileOperationJob::totalAmount(uint64_t& fileSize, uint64_t& fileCount) const {
std::lock_guard<std::mutex> lock{mutex_};
if(hasTotalAmount_) {
fileSize = totalSize_;
fileCount = totalCount_;
}
return hasTotalAmount_;
}
bool FileOperationJob::currentFileProgress(FilePath& path, uint64_t& totalSize, uint64_t& finishedSize) const {
std::lock_guard<std::mutex> lock{mutex_};
if(currentFile_.isValid()) {
path = currentFile_;
totalSize = currentFileSize_;
finishedSize = currentFileFinished_;
}
return currentFile_.isValid();
}
double FileOperationJob::progress() const {
std::lock_guard<std::mutex> lock{mutex_};
double finishedRatio;
if(calcProgressUsingSize_) {
finishedRatio = totalSize_ > 0 ? double(finishedSize_ + currentFileFinished_) / totalSize_ : 0.0;
}
else {
finishedRatio = totalCount_ > 0 ? double(finishedCount_) / totalCount_ : 0.0;
}
if(finishedRatio > 1.0) {
finishedRatio = 1.0;
}
return finishedRatio;
}
FileOperationJob::FileExistsAction FileOperationJob::askRename(const FileInfo &src, const FileInfo &dest, FilePath &newDest) {
FileExistsAction action = SKIP;
Q_EMIT fileExists(src, dest, action, newDest);
return action;
}
bool FileOperationJob::finishedAmount(uint64_t& finishedSize, uint64_t& finishedCount) const {
std::lock_guard<std::mutex> lock{mutex_};
if(hasTotalAmount_) {
finishedSize = finishedSize_;
finishedCount = finishedCount_;
}
return hasTotalAmount_;
}
void FileOperationJob::setTotalAmount(uint64_t fileSize, uint64_t fileCount) {
std::lock_guard<std::mutex> lock{mutex_};
hasTotalAmount_ = true;
totalSize_ = fileSize;
totalCount_ = fileCount;
}
void FileOperationJob::setFinishedAmount(uint64_t finishedSize, uint64_t finishedCount) {
std::lock_guard<std::mutex> lock{mutex_};
finishedSize_ = finishedSize;
finishedCount_ = finishedCount;
}
void FileOperationJob::addFinishedAmount(uint64_t finishedSize, uint64_t finishedCount) {
std::lock_guard<std::mutex> lock{mutex_};
finishedSize_ += finishedSize;
finishedCount_ += finishedCount;
}
FilePath FileOperationJob::currentFile() const {
std::lock_guard<std::mutex> lock{mutex_};
auto ret = currentFile_;
return ret;
}
void FileOperationJob::setCurrentFile(const FilePath& path) {
std::lock_guard<std::mutex> lock{mutex_};
currentFile_ = path;
}
void FileOperationJob::setCurrentFileProgress(uint64_t totalSize, uint64_t finishedSize) {
std::lock_guard<std::mutex> lock{mutex_};
currentFileSize_ = totalSize;
currentFileFinished_ = finishedSize;
}
} // namespace Fm

@ -1,96 +0,0 @@
#ifndef FM2_FILEOPERATIONJOB_H
#define FM2_FILEOPERATIONJOB_H
#include "../libfmqtglobals.h"
#include "job.h"
#include <string>
#include <mutex>
#include <cstdint>
#include "fileinfo.h"
#include "filepath.h"
namespace Fm {
class LIBFM_QT_API FileOperationJob : public Fm::Job {
Q_OBJECT
public:
enum FileExistsAction {
CANCEL = 0,
OVERWRITE = 1<<0,
RENAME = 1<<1,
SKIP = 1<<2,
SKIP_ERROR = 1<<3
};
explicit FileOperationJob();
// get total amount of work to do
bool totalAmount(std::uint64_t& fileSize, std::uint64_t& fileCount) const;
// get currently finished job amount
bool finishedAmount(std::uint64_t& finishedSize, std::uint64_t& finishedCount) const;
// get the current file
FilePath currentFile() const;
// get progress of the current file
bool currentFileProgress(FilePath& path, std::uint64_t& totalSize, std::uint64_t& finishedSize) const;
// is the job calculate progress based on file size or file counts
bool calcProgressUsingSize() const {
return calcProgressUsingSize_;
}
// get currently finished amount (0.0 to 1.0)
virtual double progress() const;
Q_SIGNALS:
void preparedToRun();
// void currentFile(const char* file);
// void progress(uint32_t percent);
// to correctly handle the signal, connect with Qt::BlockingQueuedConnection so it's a sync call.
void fileExists(const FileInfo& src, const FileInfo& dest, FileExistsAction& response, FilePath& newDest);
protected:
FileExistsAction askRename(const FileInfo& src, const FileInfo& dest, FilePath& newDest);
void setTotalAmount(std::uint64_t fileSize, std::uint64_t fileCount);
void setFinishedAmount(std::uint64_t finishedSize, std::uint64_t finishedCount);
void addFinishedAmount(std::uint64_t finishedSize, std::uint64_t finishedCount);
void setCurrentFile(const FilePath &path);
void setCurrentFileProgress(uint64_t totalSize, uint64_t finishedSize);
void setCalcProgressUsingSize(bool value) {
calcProgressUsingSize_ = value;
}
std::mutex& mutex() {
return mutex_;
}
private:
bool hasTotalAmount_;
bool calcProgressUsingSize_;
std::uint64_t totalSize_;
std::uint64_t totalCount_;
std::uint64_t finishedSize_;
std::uint64_t finishedCount_;
FilePath currentFile_;
std::uint64_t currentFileSize_;
std::uint64_t currentFileFinished_;
mutable std::mutex mutex_;
};
} // namespace Fm
#endif // FM2_FILEOPERATIONJOB_H

@ -1,21 +0,0 @@
#include "filepath.h"
#include <cstdlib>
#include <utility>
#include <glib.h>
namespace Fm {
FilePath FilePath::homeDir_;
const FilePath &FilePath::homeDir() {
if(!homeDir_) {
const char* home = getenv("HOME");
if(!home) {
home = g_get_home_dir();
}
homeDir_ = FilePath::fromLocalPath(home);
}
return homeDir_;
}
} // namespace Fm

@ -1,177 +0,0 @@
#ifndef FM2_FILEPATH_H
#define FM2_FILEPATH_H
#include "../libfmqtglobals.h"
#include "gobjectptr.h"
#include "cstrptr.h"
#include <gio/gio.h>
#include <vector>
#include <QMetaType>
namespace Fm {
class LIBFM_QT_API FilePath {
public:
explicit FilePath() {
}
explicit FilePath(GFile* gfile, bool add_ref): gfile_{gfile, add_ref} {
}
FilePath(const FilePath& other): FilePath{} {
*this = other;
}
FilePath(FilePath&& other) noexcept: FilePath{} {
*this = other;
}
static FilePath fromUri(const char* uri) {
return FilePath{g_file_new_for_uri(uri), false};
}
static FilePath fromLocalPath(const char* path) {
return FilePath{g_file_new_for_path(path), false};
}
static FilePath fromDisplayName(const char* path) {
return FilePath{g_file_parse_name(path), false};
}
static FilePath fromPathStr(const char* path_str) {
return FilePath{g_file_new_for_commandline_arg(path_str), false};
}
bool isValid() const {
return gfile_ != nullptr;
}
unsigned int hash() const {
return g_file_hash(gfile_.get());
}
CStrPtr baseName() const {
return CStrPtr{g_file_get_basename(gfile_.get())};
}
CStrPtr localPath() const {
return CStrPtr{g_file_get_path(gfile_.get())};
}
CStrPtr uri() const {
return CStrPtr{g_file_get_uri(gfile_.get())};
}
CStrPtr toString() const {
if(isNative()) {
return localPath();
}
return uri();
}
// a human readable UTF-8 display name for the path
CStrPtr displayName() const {
return CStrPtr{g_file_get_parse_name(gfile_.get())};
}
FilePath parent() const {
return FilePath{g_file_get_parent(gfile_.get()), false};
}
bool hasParent() const {
return g_file_has_parent(gfile_.get(), nullptr);
}
bool isParentOf(const FilePath& other) {
return g_file_has_parent(other.gfile_.get(), gfile_.get());
}
bool isPrefixOf(const FilePath& other) {
return g_file_has_prefix(other.gfile_.get(), gfile_.get());
}
FilePath child(const char* name) const {
return FilePath{g_file_get_child(gfile_.get(), name), false};
}
CStrPtr relativePathStr(const FilePath& descendant) const {
return CStrPtr{g_file_get_relative_path(gfile_.get(), descendant.gfile_.get())};
}
FilePath relativePath(const char* relPath) const {
return FilePath{g_file_resolve_relative_path(gfile_.get(), relPath), false};
}
bool isNative() const {
return g_file_is_native(gfile_.get());
}
bool hasUriScheme(const char* scheme) const {
return g_file_has_uri_scheme(gfile_.get(), scheme);
}
CStrPtr uriScheme() const {
return CStrPtr{g_file_get_uri_scheme(gfile_.get())};
}
const GObjectPtr<GFile>& gfile() const {
return gfile_;
}
FilePath& operator = (const FilePath& other) {
gfile_ = other.gfile_;
return *this;
}
FilePath& operator = (const FilePath&& other) noexcept {
gfile_ = std::move(other.gfile_);
return *this;
}
bool operator == (const FilePath& other) const {
return operator==(other.gfile_.get());
}
bool operator == (GFile* other_gfile) const {
if(gfile_ == other_gfile) {
return true;
}
if(gfile_ && other_gfile) {
return g_file_equal(gfile_.get(), other_gfile);
}
return false;
}
bool operator != (const FilePath& other) const {
return !operator==(other);
}
bool operator != (std::nullptr_t) const {
return gfile_ != nullptr;
}
operator bool() const {
return gfile_ != nullptr;
}
static const FilePath& homeDir();
private:
GObjectPtr<GFile> gfile_;
static FilePath homeDir_;
};
struct FilePathHash {
std::size_t operator() (const FilePath& path) const {
return path.hash();
}
};
typedef std::vector<FilePath> FilePathList;
} // namespace Fm
Q_DECLARE_METATYPE(Fm::FilePath)
#endif // FM2_FILEPATH_H

@ -1,24 +0,0 @@
#include "filesysteminfojob.h"
#include "gobjectptr.h"
namespace Fm {
void FileSystemInfoJob::exec() {
GObjectPtr<GFileInfo> inf = GObjectPtr<GFileInfo>{
g_file_query_filesystem_info(
path_.gfile().get(),
G_FILE_ATTRIBUTE_FILESYSTEM_SIZE","
G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
cancellable().get(), nullptr),
false
};
if(!inf)
return;
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_SIZE)) {
size_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
freeSize_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
isAvailable_ = true;
}
}
} // namespace Fm

@ -1,45 +0,0 @@
#ifndef FM2_FILESYSTEMINFOJOB_H
#define FM2_FILESYSTEMINFOJOB_H
#include "../libfmqtglobals.h"
#include "job.h"
#include "filepath.h"
namespace Fm {
class LIBFM_QT_API FileSystemInfoJob : public Job {
Q_OBJECT
public:
explicit FileSystemInfoJob(const FilePath& path):
path_{path},
isAvailable_{false},
size_{0},
freeSize_{0} {
}
bool isAvailable() const {
return isAvailable_;
}
uint64_t size() const {
return size_;
}
uint64_t freeSize() const {
return freeSize_;
}
protected:
void exec() override;
private:
FilePath path_;
bool isAvailable_;
uint64_t size_;
uint64_t freeSize_;
};
} // namespace Fm
#endif // FM2_FILESYSTEMINFOJOB_H

@ -1,641 +0,0 @@
#include "filetransferjob.h"
#include "totalsizejob.h"
#include "fileinfo_p.h"
namespace Fm {
FileTransferJob::FileTransferJob(FilePathList srcPaths, Mode mode):
FileOperationJob{},
srcPaths_{std::move(srcPaths)},
mode_{mode} {
}
FileTransferJob::FileTransferJob(FilePathList srcPaths, FilePathList destPaths, Mode mode):
FileTransferJob{std::move(srcPaths), mode} {
destPaths_ = std::move(destPaths);
}
FileTransferJob::FileTransferJob(FilePathList srcPaths, const FilePath& destDirPath, Mode mode):
FileTransferJob{std::move(srcPaths), mode} {
setDestDirPath(destDirPath);
}
void FileTransferJob::setSrcPaths(FilePathList srcPaths) {
srcPaths_ = std::move(srcPaths);
}
void FileTransferJob::setDestPaths(FilePathList destPaths) {
destPaths_ = std::move(destPaths);
}
void FileTransferJob::setDestDirPath(const FilePath& destDirPath) {
destPaths_.clear();
destPaths_.reserve(srcPaths_.size());
for(const auto& srcPath: srcPaths_) {
FilePath destPath;
if(mode_ == Mode::LINK && !srcPath.isNative()) {
// special handling for URLs
auto fullBasename = srcPath.baseName();
char* basename = fullBasename.get();
char* dname = nullptr;
// if we drop URI query onto native filesystem, omit query part
if(!srcPath.isNative()) {
dname = strchr(basename, '?');
}
// if basename consist only from query then use first part of it
if(dname == basename) {
basename++;
dname = strchr(basename, '&');
}
CStrPtr _basename;
if(dname) {
_basename = CStrPtr{g_strndup(basename, dname - basename)};
dname = strrchr(_basename.get(), G_DIR_SEPARATOR);
g_debug("cutting '%s' to '%s'", basename, dname ? &dname[1] : _basename.get());
if(dname) {
basename = &dname[1];
}
else {
basename = _basename.get();
}
}
destPath = destDirPath.child(basename);
}
else {
destPath = destDirPath.child(srcPath.baseName().get());
}
destPaths_.emplace_back(std::move(destPath));
}
}
void FileTransferJob::gfileCopyProgressCallback(goffset current_num_bytes, goffset total_num_bytes, FileTransferJob* _this) {
_this->setCurrentFileProgress(total_num_bytes, current_num_bytes);
}
bool FileTransferJob::moveFileSameFs(const FilePath& srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath) {
int flags = G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS;
GErrorPtr err;
bool retry;
do {
retry = false;
err.reset();
// do the file operation
if(!g_file_move(srcPath.gfile().get(), destPath.gfile().get(), GFileCopyFlags(flags), cancellable().get(),
nullptr, this, &err)) {
retry = handleError(err, srcPath, srcInfo, destPath, flags);
}
else {
return true;
}
} while(retry && !isCancelled());
return false;
}
bool FileTransferJob::copyRegularFile(const FilePath& srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath) {
int flags = G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS;
GErrorPtr err;
bool retry;
do {
retry = false;
err.reset();
// reset progress of the current file (only for copy)
auto size = g_file_info_get_size(srcInfo.get());
setCurrentFileProgress(size, 0);
// do the file operation
if(!g_file_copy(srcPath.gfile().get(), destPath.gfile().get(), GFileCopyFlags(flags), cancellable().get(),
(GFileProgressCallback)&gfileCopyProgressCallback, this, &err)) {
retry = handleError(err, srcPath, srcInfo, destPath, flags);
}
else {
return true;
}
} while(retry && !isCancelled());
return false;
}
bool FileTransferJob::copySpecialFile(const FilePath& srcPath, const GFileInfoPtr& srcInfo, FilePath &destPath) {
bool ret = false;
// only handle FIFO for local files
if(srcPath.isNative() && destPath.isNative()) {
auto src_path = srcPath.localPath();
struct stat src_st;
int r;
r = lstat(src_path.get(), &src_st);
if(r == 0) {
// Handle FIFO on native file systems.
if(S_ISFIFO(src_st.st_mode)) {
auto dest_path = destPath.localPath();
if(mkfifo(dest_path.get(), src_st.st_mode) == 0) {
ret = true;
}
}
// FIXME: how about block device, char device, and socket?
}
}
if(!ret) {
GErrorPtr err;
g_set_error(&err, G_IO_ERROR, G_IO_ERROR_FAILED,
("Cannot copy file '%s': not supported"),
g_file_info_get_display_name(srcInfo.get()));
emitError(err, ErrorSeverity::MODERATE);
}
return ret;
}
bool FileTransferJob::copyDirContent(const FilePath& srcPath, GFileInfoPtr srcInfo, FilePath& destPath, bool skip) {
bool ret = false;
// copy dir content
GErrorPtr err;
auto enu = GFileEnumeratorPtr{
g_file_enumerate_children(srcPath.gfile().get(),
defaultGFileInfoQueryAttribs,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err),
false};
if(enu) {
int n_children = 0;
int n_copied = 0;
ret = true;
while(!isCancelled()) {
err.reset();
GFileInfoPtr inf{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false};
if(inf) {
++n_children;
const char* name = g_file_info_get_name(inf.get());
FilePath childPath = srcPath.child(name);
bool child_ret = copyFile(childPath, inf, destPath, name, skip);
if(child_ret) {
++n_copied;
}
else {
ret = false;
}
}
else {
if(err) {
// fail to read directory content
// NOTE: since we cannot read the source dir, we cannot calculate the progress correctly, either.
emitError(err, ErrorSeverity::MODERATE);
err.reset();
/* ErrorAction::RETRY is not supported here */
ret = false;
}
else { /* EOF is reached */
/* all files are successfully copied. */
if(isCancelled()) {
ret = false;
}
else {
/* some files are not copied */
if(n_children != n_copied) {
/* if the copy actions are skipped deliberately, it's ok */
if(!skip) {
ret = false;
}
}
/* else job->skip_dir_content is true */
}
break;
}
}
}
g_file_enumerator_close(enu.get(), nullptr, &err);
}
else {
if(err) {
emitError(err, ErrorSeverity::MODERATE);
}
}
return ret;
}
bool FileTransferJob::makeDir(const FilePath& srcPath, GFileInfoPtr srcInfo, FilePath& destPath) {
if(isCancelled()) {
return false;
}
bool mkdir_done = false;
do {
GErrorPtr err;
mkdir_done = g_file_make_directory_with_parents(destPath.gfile().get(), cancellable().get(), &err);
if(!mkdir_done) {
if(err->domain == G_IO_ERROR && (err->code == G_IO_ERROR_EXISTS ||
err->code == G_IO_ERROR_INVALID_FILENAME ||
err->code == G_IO_ERROR_FILENAME_TOO_LONG)) {
GFileInfoPtr destInfo = GFileInfoPtr {
g_file_query_info(destPath.gfile().get(),
defaultGFileInfoQueryAttribs,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), nullptr),
false
};
if(!destInfo) {
// FIXME: error handling
break;
}
FilePath newDestPath;
FileExistsAction opt = askRename(FileInfo{srcInfo, srcPath.parent()}, FileInfo{destInfo, destPath.parent()}, newDestPath);
switch(opt) {
case FileOperationJob::RENAME:
destPath = std::move(newDestPath);
break;
case FileOperationJob::SKIP:
/* when a dir is skipped, we need to know its total size to calculate correct progress */
mkdir_done = true; /* pretend that dir creation succeeded */
break;
case FileOperationJob::OVERWRITE:
mkdir_done = true; /* pretend that dir creation succeeded */
break;
case FileOperationJob::CANCEL:
cancel();
return false;
case FileOperationJob::SKIP_ERROR: ; /* FIXME */
}
}
else {
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
if(act != ErrorAction::RETRY) {
break;
}
}
}
} while(!mkdir_done && !isCancelled());
bool chmod_done = false;
if(mkdir_done && !isCancelled()) {
mode_t mode = g_file_info_get_attribute_uint32(srcInfo.get(), G_FILE_ATTRIBUTE_UNIX_MODE);
if(mode) {
mode |= (S_IRUSR | S_IWUSR); /* ensure we have rw permission to this file. */
do {
GErrorPtr err;
// chmod the newly created dir properly
// if(!fm_job_is_cancelled(fmjob) && !job->skip_dir_content)
chmod_done = g_file_set_attribute_uint32(destPath.gfile().get(),
G_FILE_ATTRIBUTE_UNIX_MODE,
mode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err);
if(!chmod_done) {
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
if(act != ErrorAction::RETRY) {
break;
}
/* FIXME: some filesystems may not support this. */
}
} while(!chmod_done && !isCancelled());
}
}
return mkdir_done && chmod_done;
}
bool FileTransferJob::handleError(GErrorPtr &err, const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath, int& flags) {
bool retry = false;
/* handle existing files or file name conflict */
if(err.domain() == G_IO_ERROR && (err.code() == G_IO_ERROR_EXISTS ||
err.code() == G_IO_ERROR_INVALID_FILENAME ||
err.code() == G_IO_ERROR_FILENAME_TOO_LONG)) {
flags &= ~G_FILE_COPY_OVERWRITE;
// get info of the existing file
GFileInfoPtr destInfo = GFileInfoPtr {
g_file_query_info(destPath.gfile().get(),
defaultGFileInfoQueryAttribs,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), nullptr),
false
};
// ask the user to rename or overwrite the existing file
if(!isCancelled() && destInfo) {
FilePath newDestPath;
FileExistsAction opt = askRename(FileInfo{srcInfo, srcPath.parent()},
FileInfo{destInfo, destPath.parent()},
newDestPath);
switch(opt) {
case FileOperationJob::RENAME:
// try a new file name
if(newDestPath.isValid()) {
destPath = std::move(newDestPath);
// FIXME: handle the error when newDestPath is invalid.
}
retry = true;
break;
case FileOperationJob::OVERWRITE:
// overwrite existing file
flags |= G_FILE_COPY_OVERWRITE;
retry = true;
err.reset();
break;
case FileOperationJob::CANCEL:
// cancel the whole job.
cancel();
break;
case FileOperationJob::SKIP:
// skip current file and don't copy it
case FileOperationJob::SKIP_ERROR: ; /* FIXME */
retry = false;
break;
}
err.reset();
}
}
// show error message
if(!isCancelled() && err) {
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
err.reset();
if(act == ErrorAction::RETRY) {
// the user wants retry the operation again
retry = true;
}
const bool is_no_space = (err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_NO_SPACE);
/* FIXME: ask to leave partial content? */
if(is_no_space) {
// run out of disk space. delete the partial content we copied.
g_file_delete(destPath.gfile().get(), cancellable().get(), nullptr);
}
}
return retry;
}
bool FileTransferJob::processPath(const FilePath& srcPath, const FilePath& destDirPath, const char* destFileName) {
GErrorPtr err;
GFileInfoPtr srcInfo = GFileInfoPtr {
g_file_query_info(srcPath.gfile().get(),
defaultGFileInfoQueryAttribs,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err),
false
};
if(!srcInfo || isCancelled()) {
// FIXME: report error
return false;
}
bool ret;
switch(mode_) {
case Mode::MOVE:
ret = moveFile(srcPath, srcInfo, destDirPath, destFileName);
break;
case Mode::COPY: {
bool deleteSrc = false;
ret = copyFile(srcPath, srcInfo, destDirPath, destFileName, deleteSrc);
break;
}
case Mode::LINK:
ret = linkFile(srcPath, srcInfo, destDirPath, destFileName);
break;
default:
ret = false;
break;
}
return ret;
}
bool FileTransferJob::moveFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName) {
setCurrentFile(srcPath);
GErrorPtr err;
GFileInfoPtr destDirInfo = GFileInfoPtr {
g_file_query_info(destDirPath.gfile().get(),
"id::filesystem",
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err),
false
};
if(!destDirInfo || isCancelled()) {
// FIXME: report errors
return false;
}
// If src and dest are on the same filesystem, do move.
// Exception: if src FS is trash:///, we always do move
// Otherwise, do copy & delete src files.
auto src_fs = g_file_info_get_attribute_string(srcInfo.get(), "id::filesystem");
auto dest_fs = g_file_info_get_attribute_string(destDirInfo.get(), "id::filesystem");
bool ret;
if(src_fs && dest_fs && (strcmp(src_fs, dest_fs) == 0 || g_str_has_prefix(src_fs, "trash"))) {
// src and dest are on the same filesystem
auto destPath = destDirPath.child(destFileName);
ret = moveFileSameFs(srcPath, srcInfo, destPath);
// increase current progress
// FIXME: it's not appropriate to calculate the progress of move operations using file size
// since the time required to move a file is not related to it's file size.
auto size = g_file_info_get_size(srcInfo.get());
addFinishedAmount(size, 1);
}
else {
// cross device/filesystem move: copy & delete
ret = copyFile(srcPath, srcInfo, destDirPath, destFileName);
// NOTE: do not need to increase progress here since it's done by copyPath().
}
return ret;
}
bool FileTransferJob::copyFile(const FilePath& srcPath, const GFileInfoPtr& srcInfo, const FilePath& destDirPath, const char* destFileName, bool skip) {
setCurrentFile(srcPath);
auto size = g_file_info_get_size(srcInfo.get());
bool success = false;
setCurrentFileProgress(size, 0);
auto destPath = destDirPath.child(destFileName);
auto file_type = g_file_info_get_file_type(srcInfo.get());
if(!skip) {
switch(file_type) {
case G_FILE_TYPE_DIRECTORY:
success = makeDir(srcPath, srcInfo, destPath);
break;
case G_FILE_TYPE_SPECIAL:
success = copySpecialFile(srcPath, srcInfo, destPath);
break;
default:
success = copyRegularFile(srcPath, srcInfo, destPath);
break;
}
}
else { // skip the file
success = true;
}
if(success) {
// finish copying the file
addFinishedAmount(size, 1);
setCurrentFileProgress(0, 0);
// recursively copy dir content
if(file_type == G_FILE_TYPE_DIRECTORY) {
success = copyDirContent(srcPath, srcInfo, destPath, skip);
}
if(!skip && success && mode_ == Mode::MOVE) {
// delete the source file for cross-filesystem move
GErrorPtr err;
if(g_file_delete(srcPath.gfile().get(), cancellable().get(), &err)) {
// FIXME: add some file size to represent the amount of work need to delete a file
addFinishedAmount(1, 1);
}
else {
success = false;
}
}
}
return success;
}
bool FileTransferJob::linkFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName) {
setCurrentFile(srcPath);
bool ret = false;
// cannot create links on non-native filesystems
if(!destDirPath.isNative()) {
auto msg = tr("Cannot create a link on non-native filesystem");
GErrorPtr err{g_error_new_literal(G_IO_ERROR, G_IO_ERROR_FAILED, msg.toUtf8().constData())};
emitError(err, ErrorSeverity::CRITICAL);
return false;
}
if(srcPath.isNative()) {
// create symlinks for native files
auto destPath = destDirPath.child(destFileName);
ret = createSymlink(srcPath, srcInfo, destPath);
}
else {
// ensure that the dest file has *.desktop filename extension.
CStrPtr desktopEntryFileName{g_strconcat(destFileName, ".desktop", nullptr)};
auto destPath = destDirPath.child(desktopEntryFileName.get());
ret = createShortcut(srcPath, srcInfo, destPath);
}
// update progress
// FIXME: increase the progress by 1 byte is not appropriate
addFinishedAmount(1, 1);
return ret;
}
bool FileTransferJob::createSymlink(const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath) {
bool ret = false;
auto src = srcPath.localPath();
int flags = 0;
GErrorPtr err;
bool retry;
do {
retry = false;
if(flags & G_FILE_COPY_OVERWRITE) { // overwrite existing file
// creating symlink cannot overwrite existing files directly, so we delete the existing file first.
g_file_delete(destPath.gfile().get(), cancellable().get(), nullptr);
}
if(!g_file_make_symbolic_link(destPath.gfile().get(), src.get(), cancellable().get(), &err)) {
retry = handleError(err, srcPath, srcInfo, destPath, flags);
}
else {
ret = true;
break;
}
} while(!isCancelled() && retry);
return ret;
}
bool FileTransferJob::createShortcut(const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath) {
bool ret = false;
const char* iconName = nullptr;
GIcon* icon = g_file_info_get_icon(srcInfo.get());
if(icon && G_IS_THEMED_ICON(icon)) {
auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(icon));
if(iconNames && iconNames[0]) {
iconName = iconNames[0];
}
}
CStrPtr srcPathUri;
auto uri = g_file_info_get_attribute_string(srcInfo.get(), G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
if(!uri) {
srcPathUri = srcPath.uri();
uri = srcPathUri.get();
}
CStrPtr srcPathDispName;
auto name = g_file_info_get_display_name(srcInfo.get());
if(!name) {
srcPathDispName = srcPath.displayName();
name = srcPathDispName.get();
}
GKeyFile* kf = g_key_file_new();
if(kf) {
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TYPE, "Link");
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, name);
if(iconName) {
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, iconName);
}
if(uri) {
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_URL, uri);
}
gsize contentLen;
CStrPtr content{g_key_file_to_data(kf, &contentLen, nullptr)};
g_key_file_free(kf);
int flags = 0;
if(content) {
bool retry;
GErrorPtr err;
do {
retry = false;
if(flags & G_FILE_COPY_OVERWRITE) { // overwrite existing file
g_file_delete(destPath.gfile().get(), cancellable().get(), nullptr);
}
if(!g_file_replace_contents(destPath.gfile().get(), content.get(), contentLen, nullptr, false, G_FILE_CREATE_NONE, nullptr, cancellable().get(), &err)) {
retry = handleError(err, srcPath, srcInfo, destPath, flags);
err.reset();
}
else {
ret = true;
}
} while(!isCancelled() && retry);
ret = true;
}
}
return ret;
}
void FileTransferJob::exec() {
// calculate the total size of files to copy
auto totalSizeFlags = (mode_ == Mode::COPY ? TotalSizeJob::DEFAULT : TotalSizeJob::PREPARE_MOVE);
TotalSizeJob totalSizeJob{srcPaths_, totalSizeFlags};
connect(&totalSizeJob, &TotalSizeJob::error, this, &FileTransferJob::error);
connect(this, &FileTransferJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel);
totalSizeJob.run();
if(isCancelled()) {
return;
}
// ready to start
setTotalAmount(totalSizeJob.totalSize(), totalSizeJob.fileCount());
Q_EMIT preparedToRun();
if(srcPaths_.size() != destPaths_.size()) {
qWarning("error: srcPaths.size() != destPaths.size() when copying files");
return;
}
// copy the files
for(size_t i = 0; i < srcPaths_.size(); ++i) {
if(isCancelled()) {
break;
}
const auto& srcPath = srcPaths_[i];
const auto& destPath = destPaths_[i];
auto destDirPath = destPath.parent();
processPath(srcPath, destDirPath, destPath.baseName().get());
}
}
} // namespace Fm

@ -1,58 +0,0 @@
#ifndef FM2_COPYJOB_H
#define FM2_COPYJOB_H
#include "../libfmqtglobals.h"
#include "fileoperationjob.h"
#include "gioptrs.h"
namespace Fm {
class LIBFM_QT_API FileTransferJob : public Fm::FileOperationJob {
Q_OBJECT
public:
enum class Mode {
COPY,
MOVE,
LINK
};
explicit FileTransferJob(FilePathList srcPaths, Mode mode = Mode::COPY);
explicit FileTransferJob(FilePathList srcPaths, FilePathList destPaths, Mode mode = Mode::COPY);
explicit FileTransferJob(FilePathList srcPaths, const FilePath &destDirPath, Mode mode = Mode::COPY);
void setSrcPaths(FilePathList srcPaths);
void setDestPaths(FilePathList destPaths);
void setDestDirPath(const FilePath &destDirPath);
protected:
void exec() override;
private:
bool processPath(const FilePath& srcPath, const FilePath& destPath, const char *destFileName);
bool moveFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName);
bool copyFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName, bool skip = false);
bool linkFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName);
bool moveFileSameFs(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath &destPath);
bool copyRegularFile(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath &destPath);
bool copySpecialFile(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath);
bool copyDirContent(const FilePath &srcPath, GFileInfoPtr srcInfo, FilePath &destPath, bool skip = false);
bool makeDir(const FilePath &srcPath, GFileInfoPtr srcInfo, FilePath &destPath);
bool createSymlink(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath);
bool createShortcut(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath);
bool handleError(GErrorPtr& err, const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath, int& flags);
static void gfileCopyProgressCallback(goffset current_num_bytes, goffset total_num_bytes, FileTransferJob* _this);
private:
FilePathList srcPaths_;
FilePathList destPaths_;
Mode mode_;
};
} // namespace Fm
#endif // FM2_COPYJOB_H

@ -1,914 +0,0 @@
/*
* fm-folder.c
*
* Copyright 2009 - 2012 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
* Copyright 2012-2016 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
*
* This file is a part of the Libfm library.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "folder.h"
#include <string.h>
#include <cassert>
#include <QTimer>
#include <QDebug>
#include "dirlistjob.h"
#include "filesysteminfojob.h"
#include "fileinfojob.h"
namespace Fm {
std::unordered_map<FilePath, std::weak_ptr<Folder>, FilePathHash> Folder::cache_;
QString Folder::cutFilesDirPath_;
QString Folder::lastCutFilesDirPath_;
std::shared_ptr<const HashSet> Folder::cutFilesHashSet_;
std::mutex Folder::mutex_;
Folder::Folder():
dirlist_job{nullptr},
fsInfoJob_{nullptr},
volumeManager_{VolumeManager::globalInstance()},
/* for file monitor */
has_idle_reload_handler{0},
has_idle_update_handler{false},
pending_change_notify{false},
filesystem_info_pending{false},
wants_incremental{false},
stop_emission{false}, /* don't set it 1 bit to not lock other bits */
/* filesystem info - set in query thread, read in main */
fs_total_size{0},
fs_free_size{0},
has_fs_info{false},
defer_content_test{false} {
connect(volumeManager_.get(), &VolumeManager::mountAdded, this, &Folder::onMountAdded);
connect(volumeManager_.get(), &VolumeManager::mountRemoved, this, &Folder::onMountRemoved);
}
Folder::Folder(const FilePath& path): Folder() {
dirPath_ = path;
}
Folder::~Folder() {
if(dirMonitor_) {
g_signal_handlers_disconnect_by_data(dirMonitor_.get(), this);
dirMonitor_.reset();
}
if(dirlist_job) {
dirlist_job->cancel();
}
// cancel any file info job in progress.
for(auto job: fileinfoJobs_) {
job->cancel();
}
if(fsInfoJob_) {
fsInfoJob_->cancel();
}
// We store a weak_ptr instead of shared_ptr in the hash table, so the hash table
// does not own a reference to the folder. When the last reference to Folder is
// freed, we need to remove its hash table entry.
std::lock_guard<std::mutex> lock{mutex_};
auto it = cache_.find(dirPath_);
if(it != cache_.end()) {
cache_.erase(it);
}
}
// static
std::shared_ptr<Folder> Folder::fromPath(const FilePath& path) {
std::lock_guard<std::mutex> lock{mutex_};
auto it = cache_.find(path);
if(it != cache_.end()) {
auto folder = it->second.lock();
if(folder) {
return folder;
}
else { // FIXME: is this possible?
cache_.erase(it);
}
}
auto folder = std::make_shared<Folder>(path);
folder->reload();
cache_.emplace(path, folder);
return folder;
}
bool Folder::makeDirectory(const char* /*name*/, GError** /*error*/) {
// TODO:
// FIXME: what the API is used for in the original libfm C API?
return false;
}
bool Folder::isIncremental() const {
return wants_incremental;
}
bool Folder::isValid() const {
return dirInfo_ != nullptr;
}
bool Folder::isLoaded() const {
return (dirlist_job == nullptr);
}
std::shared_ptr<const FileInfo> Folder::fileByName(const char* name) const {
auto it = files_.find(name);
if(it != files_.end()) {
return it->second;
}
return nullptr;
}
bool Folder::isEmpty() const {
return files_.empty();
}
FileInfoList Folder::files() const {
FileInfoList ret;
ret.reserve(files_.size());
for(const auto& item : files_) {
ret.push_back(item.second);
}
return ret;
}
const FilePath& Folder::path() const {
auto pathStr = dirPath_.toString();
// qDebug() << this << "FOLDER_PATH:" << pathStr.get() << dirPath_.gfile().get();
//assert(!g_str_has_prefix(pathStr.get(), "file:"));
return dirPath_;
}
const std::shared_ptr<const FileInfo>& Folder::info() const {
return dirInfo_;
}
#if 0
void Folder::init(FmFolder* folder) {
files = fm_file_info_list_new();
G_LOCK(hash);
if(G_UNLIKELY(hash_uses == 0)) {
hash = g_hash_table_new((GHashFunc)fm_path_hash, (GEqualFunc)fm_path_equal);
volume_monitor = g_volume_monitor_get();
if(G_LIKELY(volume_monitor)) {
g_signal_connect(volume_monitor, "mount-added", G_CALLBACK(on_mount_added), nullptr);
g_signal_connect(volume_monitor, "mount-removed", G_CALLBACK(on_mount_removed), nullptr);
}
}
hash_uses++;
G_UNLOCK(hash);
}
#endif
void Folder::onIdleReload() {
/* check if folder still exists */
reload();
// G_LOCK(query);
has_idle_reload_handler = false;
// G_UNLOCK(query);
}
void Folder::queueReload() {
// G_LOCK(query);
if(!has_idle_reload_handler) {
has_idle_reload_handler = true;
QTimer::singleShot(0, this, &Folder::onIdleReload);
}
// G_UNLOCK(query);
}
void Folder::onFileInfoFinished() {
FileInfoJob* job = static_cast<FileInfoJob*>(sender());
fileinfoJobs_.erase(std::find(fileinfoJobs_.cbegin(), fileinfoJobs_.cend(), job));
if(job->isCancelled())
return;
FileInfoList files_to_add;
FileInfoList files_to_delete;
std::vector<FileInfoPair> files_to_update;
const auto& paths = job->paths();
const auto& deletionPaths = job->deletionPaths();
const auto& infos = job->files();
auto path_it = paths.cbegin();
auto info_it = infos.cbegin();
for(; path_it != paths.cend() && info_it != infos.cend(); ++path_it, ++info_it) {
const auto& path = *path_it;
const auto& info = *info_it;
if(path == dirPath_) { // got the info for the folder itself.
dirInfo_ = info;
}
// add/update the file only if it isn't going to be deleted
else if(std::find(deletionPaths.cbegin(), deletionPaths.cend(), path) == deletionPaths.cend()) {
auto it = files_.find(info->name());
if(it != files_.end()) { // the file already exists, update
files_to_update.push_back(std::make_pair(it->second, info));
}
else { // newly added
files_to_add.push_back(info);
}
files_[info->name()] = info;
}
}
if(!files_to_add.empty()) {
Q_EMIT filesAdded(files_to_add);
}
if(!files_to_update.empty()) {
Q_EMIT filesChanged(files_to_update);
}
// deletion should be done now, after the info job is processed
for(const auto &path: deletionPaths) {
auto name = path.baseName();
auto it = files_.find(name.get());
if(it != files_.end()) {
files_to_delete.push_back(it->second);
files_.erase(it);
}
}
if(!files_to_delete.empty()) {
Q_EMIT filesRemoved(files_to_delete);
}
Q_EMIT contentChanged();
}
void Folder::processPendingChanges() {
has_idle_update_handler = false;
// FmFileInfoJob* job = nullptr;
std::lock_guard<std::mutex> lock{mutex_};
// idle_handler = 0;
/* if we were asked to block updates let delay it for now */
if(stop_emission) {
return;
}
FileInfoJob* info_job = nullptr;
if(!paths_to_update.empty() || !paths_to_add.empty() || !paths_to_del.empty()) {
FilePathList paths, deletionPaths;
paths.insert(paths.end(), paths_to_add.cbegin(), paths_to_add.cend());
paths.insert(paths.end(), paths_to_update.cbegin(), paths_to_update.cend());
deletionPaths.insert(deletionPaths.end(), paths_to_del.cbegin(), paths_to_del.cend());
info_job = new FileInfoJob{paths, deletionPaths, dirPath_,
hasCutFiles() ? cutFilesHashSet_ : nullptr};
paths_to_update.clear();
paths_to_add.clear();
paths_to_del.clear();
}
if(info_job) {
fileinfoJobs_.push_back(info_job);
info_job->setAutoDelete(true);
connect(info_job, &FileInfoJob::finished, this, &Folder::onFileInfoFinished, Qt::BlockingQueuedConnection);
info_job->runAsync();
#if 0
pending_jobs = g_slist_prepend(pending_jobs, job);
if(!fm_job_run_async(FM_JOB(job))) {
pending_jobs = g_slist_remove(pending_jobs, job);
g_object_unref(job);
g_critical("failed to start folder update job");
}
#endif
}
if(pending_change_notify) {
Q_EMIT changed();
/* update volume info */
queryFilesystemInfo();
pending_change_notify = false;
}
if(filesystem_info_pending) {
Q_EMIT fileSystemChanged();
filesystem_info_pending = false;
}
}
/* should be called only with G_LOCK(lists) on! */
void Folder::queueUpdate() {
// qDebug() << "queue_update:" << !has_idle_handler << paths_to_add.size() << paths_to_update.size() << paths_to_del.size();
if(!has_idle_update_handler) {
QTimer::singleShot(0, this, &Folder::processPendingChanges);
has_idle_update_handler = true;
}
}
/* returns true if reference was taken from path */
bool Folder::eventFileAdded(const FilePath &path) {
bool added = true;
// G_LOCK(lists);
/* make sure that the file is not already queued for addition. */
if(std::find(paths_to_add.cbegin(), paths_to_add.cend(), path) == paths_to_add.cend()) {
if(files_.find(path.baseName().get()) != files_.end()) { // the file already exists, update instead
if(std::find(paths_to_update.cbegin(), paths_to_update.cend(), path) == paths_to_update.cend()) {
paths_to_update.push_back(path);
}
}
else { // newly added file
paths_to_add.push_back(path);
}
/* bug #3591771: 'ln -fns . test' leave no file visible in folder.
If it is queued for deletion then cancel that operation */
paths_to_del.erase(std::remove(paths_to_del.begin(), paths_to_del.end(), path), paths_to_del.cend());
}
else
/* file already queued for adding, don't duplicate */
{
added = false;
}
if(added) {
queueUpdate();
}
// G_UNLOCK(lists);
return added;
}
bool Folder::eventFileChanged(const FilePath &path) {
bool added;
// G_LOCK(lists);
/* make sure that the file is not already queued for changes, addition or deletion */
if(std::find(paths_to_update.cbegin(), paths_to_update.cend(), path) == paths_to_update.cend()
&& std::find(paths_to_add.cbegin(), paths_to_add.cend(), path) == paths_to_add.cend()
&& std::find(paths_to_del.cbegin(), paths_to_del.cend(), path) == paths_to_del.cend()) {
/* Since this function is called only when a file already exists, even if that file
isn't included in "files_" yet, it will be soon due to a previous call to queueUpdate().
So, here, we should queue it for changes regardless of what "files_" may contain. */
paths_to_update.push_back(path);
added = true;
queueUpdate();
}
else {
added = false;
}
// G_UNLOCK(lists);
return added;
}
void Folder::eventFileDeleted(const FilePath& path) {
// qDebug() << "delete " << path.baseName().get();
// G_LOCK(lists);
/* Queue the file for deletion only if it is not in the addition queue but
if it is, remove it from that queue instead of queueing it for deletion.
Moreover, as was the case with eventFileChanged(), here too queueing
should be done regardless of what "files_" may contain. */
if(std::find(paths_to_add.cbegin(), paths_to_add.cend(), path) == paths_to_add.cend()) {
if(std::find(paths_to_del.cbegin(), paths_to_del.cend(), path) == paths_to_del.cend()) {
paths_to_del.push_back(path);
}
}
else {
paths_to_add.erase(std::remove(paths_to_add.begin(), paths_to_add.end(), path), paths_to_add.cend());
}
/* the update queue should be canceled for a file that is going to be deleted */
paths_to_update.erase(std::remove(paths_to_update.begin(), paths_to_update.end(), path), paths_to_update.cend());
queueUpdate();
// G_UNLOCK(lists);
}
void Folder::onDirChanged(GFileMonitorEvent evt) {
switch(evt) {
case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
/* g_debug("folder is going to be unmounted"); */
break;
case G_FILE_MONITOR_EVENT_UNMOUNTED:
Q_EMIT unmount();
/* g_debug("folder is unmounted"); */
queueReload();
break;
case G_FILE_MONITOR_EVENT_DELETED:
Q_EMIT removed();
/* g_debug("folder is deleted"); */
break;
case G_FILE_MONITOR_EVENT_CREATED:
queueReload();
break;
case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
case G_FILE_MONITOR_EVENT_CHANGED: {
std::lock_guard<std::mutex> lock{mutex_};
pending_change_notify = true;
if(std::find(paths_to_update.cbegin(), paths_to_update.cend(), dirPath_) != paths_to_update.cend()) {
paths_to_update.push_back(dirPath_);
queueUpdate();
}
/* g_debug("folder is changed"); */
break;
}
#if GLIB_CHECK_VERSION(2,24,0)
case G_FILE_MONITOR_EVENT_MOVED:
#endif
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
;
default:
break;
}
}
void Folder::onFileChangeEvents(GFileMonitor* /*monitor*/, GFile* gf, GFile* /*other_file*/, GFileMonitorEvent evt) {
/* const char* names[]={
"G_FILE_MONITOR_EVENT_CHANGED",
"G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT",
"G_FILE_MONITOR_EVENT_DELETED",
"G_FILE_MONITOR_EVENT_CREATED",
"G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED",
"G_FILE_MONITOR_EVENT_PRE_UNMOUNT",
"G_FILE_MONITOR_EVENT_UNMOUNTED"
}; */
if(dirPath_ == gf) {
onDirChanged(evt);
return;
}
else {
std::lock_guard<std::mutex> lock{mutex_};
auto path = FilePath{gf, true};
/* NOTE: sometimes, for unknown reasons, GFileMonitor gives us the
* same event of the same file for multiple times. So we need to
* check for duplications ourselves here. */
switch(evt) {
case G_FILE_MONITOR_EVENT_CREATED:
eventFileAdded(path);
break;
case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
case G_FILE_MONITOR_EVENT_CHANGED:
eventFileChanged(path);
break;
case G_FILE_MONITOR_EVENT_DELETED:
eventFileDeleted(path);
break;
default:
break;
}
}
}
// checks whether there were cut files here
// and if there were, invalidates this last cut path
bool Folder::hadCutFilesUnset() {
if(lastCutFilesDirPath_ == dirPath_.toString().get()) {
lastCutFilesDirPath_ = QString();
return true;
}
return false;
}
bool Folder::hasCutFiles() {
return cutFilesHashSet_
&& !cutFilesHashSet_->empty()
&& cutFilesDirPath_ == dirPath_.toString().get();
}
void Folder::setCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet) {
if(cutFilesHashSet_ && !cutFilesHashSet_->empty()) {
lastCutFilesDirPath_ = cutFilesDirPath_;
}
cutFilesDirPath_ = dirPath_.toString().get();
cutFilesHashSet_ = cutFilesHashSet;
}
void Folder::onDirListFinished() {
DirListJob* job = static_cast<DirListJob*>(sender());
if(job->isCancelled()) { // this is a cancelled job, ignore!
if(job == dirlist_job) {
dirlist_job = nullptr;
}
Q_EMIT finishLoading();
return;
}
dirInfo_ = job->dirInfo();
FileInfoList files_to_add;
std::vector<FileInfoPair> files_to_update;
const auto& infos = job->files();
// with "search://", there is no update for infos and all of them should be added
if(strcmp(dirPath_.uriScheme().get(), "search") == 0) {
files_to_add = infos;
for(auto& file: files_to_add) {
files_[file->name()] = file;
}
}
else {
auto info_it = infos.cbegin();
for(; info_it != infos.cend(); ++info_it) {
const auto& info = *info_it;
auto it = files_.find(info->name());
if(it != files_.end()) {
files_to_update.push_back(std::make_pair(it->second, info));
}
else {
files_to_add.push_back(info);
}
files_[info->name()] = info;
}
}
if(!files_to_add.empty()) {
Q_EMIT filesAdded(files_to_add);
}
if(!files_to_update.empty()) {
Q_EMIT filesChanged(files_to_update);
}
#if 0
if(dirlist_job->isCancelled() && !wants_incremental) {
GList* l;
for(l = fm_file_info_list_peek_head_link(job->files); l; l = l->next) {
FmFileInfo* inf = (FmFileInfo*)l->data;
files = g_slist_prepend(files, inf);
fm_file_info_list_push_tail(files, inf);
}
if(G_LIKELY(files)) {
GSList* l;
G_LOCK(lists);
if(defer_content_test && fm_path_is_native(dir_path))
/* we got only basic info on content, schedule update it now */
for(l = files; l; l = l->next)
files_to_update = g_slist_prepend(files_to_update,
fm_path_ref(fm_file_info_get_path(l->data)));
G_UNLOCK(lists);
g_signal_emit(folder, signals[FILES_ADDED], 0, files);
g_slist_free(files);
}
if(job->dir_fi) {
dir_fi = fm_file_info_ref(job->dir_fi);
}
/* Some new files are created while FmDirListJob is loading the folder. */
G_LOCK(lists);
if(G_UNLIKELY(files_to_add)) {
/* This should be a very rare case. Could this happen? */
GSList* l;
for(l = files_to_add; l;) {
FmPath* path = l->data;
GSList* next = l->next;
if(_Folder::get_file_by_path(folder, path)) {
/* we already have the file. remove it from files_to_add,
* and put it in files_to_update instead.
* No strdup for name is needed here. We steal
* the string from files_to_add.*/
files_to_update = g_slist_prepend(files_to_update, path);
files_to_add = g_slist_delete_link(files_to_add, l);
}
l = next;
}
}
G_UNLOCK(lists);
}
else if(!dir_fi && job->dir_fi)
/* we may need dir_fi for incremental folders too */
{
dir_fi = fm_file_info_ref(job->dir_fi);
}
g_object_unref(dirlist_job);
#endif
dirlist_job = nullptr;
Q_EMIT finishLoading();
}
#if 0
void on_dirlist_job_files_found(FmDirListJob* job, GSList* files, gpointer user_data) {
FmFolder* folder = FM_FOLDER(user_data);
GSList* l;
for(l = files; l; l = l->next) {
FmFileInfo* file = FM_FILE_INFO(l->data);
fm_file_info_list_push_tail(files, file);
}
if(G_UNLIKELY(!dir_fi && job->dir_fi))
/* we may want info while folder is still loading */
{
dir_fi = fm_file_info_ref(job->dir_fi);
}
g_signal_emit(folder, signals[FILES_ADDED], 0, files);
}
ErrorAction on_dirlist_job_error(FmDirListJob* job, GError* err, FmJobErrorSeverity severity, FmFolder* folder) {
guint ret;
/* it's possible that some signal handlers tries to free the folder
* when errors occurs, so let's g_object_ref here. */
g_object_ref(folder);
g_signal_emit(folder, signals[ERROR], 0, err, (guint)severity, &ret);
g_object_unref(folder);
return ret;
}
void free_dirlist_job(FmFolder* folder) {
if(wants_incremental) {
g_signal_handlers_disconnect_by_func(dirlist_job, on_dirlist_job_files_found, folder);
}
g_signal_handlers_disconnect_by_func(dirlist_job, on_dirlist_job_finished, folder);
g_signal_handlers_disconnect_by_func(dirlist_job, on_dirlist_job_error, folder);
fm_job_cancel(FM_JOB(dirlist_job));
g_object_unref(dirlist_job);
dirlist_job = nullptr;
}
#endif
void Folder::reload() {
// cancel in-progress jobs if there are any
GError* err = nullptr;
// cancel directory monitoring
if(dirMonitor_) {
g_signal_handlers_disconnect_by_data(dirMonitor_.get(), this);
dirMonitor_.reset();
}
/* clear all update-lists now, see SF bug #919 - if update comes before
listing job is finished, a duplicate may be created in the folder */
if(has_idle_update_handler) {
// FIXME: cancel the idle handler
paths_to_add.clear();
paths_to_update.clear();
paths_to_del.clear();
// cancel any file info job in progress.
for(auto job: fileinfoJobs_) {
job->cancel();
disconnect(job, &FileInfoJob::finished, this, &Folder::onFileInfoFinished);
}
fileinfoJobs_.clear();
}
/* remove all existing files */
if(!files_.empty()) {
// FIXME: this is not very efficient :(
auto tmp = files();
files_.clear();
Q_EMIT filesRemoved(tmp);
}
/* Tell the world that we're about to reload the folder.
* It might be a good idea for users of the folder to disconnect
* from the folder temporarily and reconnect to it again after
* the folder complete the loading. This might reduce some
* unnecessary signal handling and UI updates. */
Q_EMIT startLoading();
dirInfo_.reset(); // clear dir info
/* also re-create a new file monitor */
// mon = GFileMonitorPtr{fm_monitor_directory(dir_path.gfile().get(), &err), false};
// FIXME: should we make this cancellable?
dirMonitor_ = GFileMonitorPtr{
g_file_monitor_directory(dirPath_.gfile().get(), G_FILE_MONITOR_WATCH_MOUNTS, nullptr, &err),
false
};
if(dirMonitor_) {
g_signal_connect(dirMonitor_.get(), "changed", G_CALLBACK(_onFileChangeEvents), this);
}
else {
qDebug("file monitor cannot be created: %s", err->message);
g_error_free(err);
}
Q_EMIT contentChanged();
/* run a new dir listing job */
// FIXME:
// defer_content_test = fm_config->defer_content_test;
dirlist_job = new DirListJob(dirPath_, defer_content_test ? DirListJob::FAST : DirListJob::DETAILED,
hasCutFiles() ? cutFilesHashSet_ : nullptr);
dirlist_job->setAutoDelete(true);
connect(dirlist_job, &DirListJob::error, this, &Folder::error, Qt::BlockingQueuedConnection);
connect(dirlist_job, &DirListJob::finished, this, &Folder::onDirListFinished, Qt::BlockingQueuedConnection);
#if 0
if(wants_incremental) {
g_signal_connect(dirlist_job, "files-found", G_CALLBACK(on_dirlist_job_files_found), folder);
}
fm_dir_list_job_set_incremental(dirlist_job, wants_incremental);
#endif
dirlist_job->runAsync();
/* also reload filesystem info.
* FIXME: is this needed? */
queryFilesystemInfo();
}
#if 0
/**
* Folder::is_incremental
* @folder: folder to test
*
* Checks if a folder is incrementally loaded.
* After an FmFolder object is obtained from calling Folder::from_path(),
* if it's not yet loaded, it begins loading the content of the folder
* and emits "start-loading" signal. Most of the time, the info of the
* files in the folder becomes available only after the folder is fully
* loaded. That means, after the "finish-loading" signal is emitted.
* Before the loading is finished, Folder::get_files() returns nothing.
* You can tell if a folder is still being loaded with Folder::is_loaded().
*
* However, for some special FmFolder types, such as the ones handling
* search:// URIs, we want to access the file infos while the folder is
* still being loaded (the search is still ongoing).
* The content of the folder grows incrementally and Folder::get_files()
* returns files currently being loaded even when the folder is not
* fully loaded. This is what we called incremental.
* Folder::is_incremental() tells you if the FmFolder has this feature.
*
* Returns: %true if @folder is incrementally loaded
*
* Since: 1.0.2
*/
bool Folder::is_incremental(FmFolder* folder) {
return wants_incremental;
}
#endif
bool Folder::getFilesystemInfo(uint64_t* total_size, uint64_t* free_size) const {
if(has_fs_info) {
*total_size = fs_total_size;
*free_size = fs_free_size;
return true;
}
return false;
}
void Folder::onFileSystemInfoFinished() {
FileSystemInfoJob* job = static_cast<FileSystemInfoJob*>(sender());
if(job->isCancelled() || job != fsInfoJob_) { // this is a cancelled job, ignore!
fsInfoJob_ = nullptr;
has_fs_info = false;
return;
}
has_fs_info = job->isAvailable();
fs_total_size = job->size();
fs_free_size = job->freeSize();
filesystem_info_pending = true;
fsInfoJob_ = nullptr;
queueUpdate();
}
void Folder::queryFilesystemInfo() {
// G_LOCK(query);
if(fsInfoJob_)
return;
fsInfoJob_ = new FileSystemInfoJob{dirPath_};
fsInfoJob_->setAutoDelete(true);
connect(fsInfoJob_, &FileSystemInfoJob::finished, this, &Folder::onFileSystemInfoFinished, Qt::BlockingQueuedConnection);
fsInfoJob_->runAsync();
// G_UNLOCK(query);
}
#if 0
/**
* Folder::block_updates
* @folder: folder to apply
*
* Blocks emitting signals for changes in folder, i.e. if some file was
* added, changed, or removed in folder after this API, no signal will be
* sent until next call to Folder::unblock_updates().
*
* Since: 1.2.0
*/
void Folder::block_updates(FmFolder* folder) {
/* g_debug("Folder::block_updates %p", folder); */
G_LOCK(lists);
/* just set the flag */
stop_emission = true;
G_UNLOCK(lists);
}
/**
* Folder::unblock_updates
* @folder: folder to apply
*
* Unblocks emitting signals for changes in folder. If some changes were
* in folder after previous call to Folder::block_updates() then these
* changes will be sent after this call.
*
* Since: 1.2.0
*/
void Folder::unblock_updates(FmFolder* folder) {
/* g_debug("Folder::unblock_updates %p", folder); */
G_LOCK(lists);
stop_emission = false;
/* query update now */
queue_update(folder);
G_UNLOCK(lists);
/* g_debug("Folder::unblock_updates OK"); */
}
/**
* Folder::make_directory
* @folder: folder to apply
* @name: display name for new directory
* @error: (allow-none) (out): location to save error
*
* Creates new directory in given @folder.
*
* Returns: %true in case of success.
*
* Since: 1.2.0
*/
bool Folder::make_directory(FmFolder* folder, const char* name, GError** error) {
GFile* dir, *gf;
FmPath* path;
bool ok;
dir = fm_path_to_gfile(dir_path);
gf = g_file_get_child_for_display_name(dir, name, error);
g_object_unref(dir);
if(gf == nullptr) {
return false;
}
ok = g_file_make_directory(gf, nullptr, error);
if(ok) {
path = fm_path_new_for_gfile(gf);
if(!_Folder::event_file_added(folder, path)) {
fm_path_unref(path);
}
}
g_object_unref(gf);
return ok;
}
void Folder::content_changed(FmFolder* folder) {
if(has_fs_info && !fs_info_not_avail) {
Folder::query_filesystem_info(folder);
}
}
#endif
/* NOTE:
* GFileMonitor has some significant limitations:
* 1. Currently it can correctly emit unmounted event for a directory.
* 2. After a directory is unmounted, its content changes.
* Inotify does not fire events for this so a forced reload is needed.
* 3. If a folder is empty, and later a filesystem is mounted to the
* folder, its content should reflect the content of the newly mounted
* filesystem. However, GFileMonitor and inotify do not emit events
* for this case. A forced reload might be needed for this case as well.
* 4. Some limitations come from Linux/inotify. If FAM/gamin is used,
* the condition may be different. More testing is needed.
*/
void Folder::onMountAdded(const Mount& mnt) {
/* If a filesystem is mounted over an existing folder,
* we need to refresh the content of the folder to reflect
* the changes. Besides, we need to create a new GFileMonitor
* for the newly-mounted filesystem as the inode already changed.
* GFileMonitor cannot detect this kind of changes caused by mounting.
* So let's do it ourselves. */
auto mountRoot = mnt.root();
if(mountRoot.isPrefixOf(dirPath_)) {
queueReload();
}
/* g_debug("FmFolder::mount_added"); */
}
void Folder::onMountRemoved(const Mount& mnt) {
/* g_debug("FmFolder::mount_removed"); */
/* NOTE: gvfs does not emit unmount signals for remote folders since
* GFileMonitor does not support remote filesystems at all.
* So here is the side effect, no unmount notifications.
* We need to generate the signal ourselves. */
if(!dirMonitor_) {
// this is only needed when we don't have a GFileMonitor
auto mountRoot = mnt.root();
if(mountRoot.isPrefixOf(dirPath_)) {
// if the current folder is under the unmounted path, generate the event ourselves
onDirChanged(G_FILE_MONITOR_EVENT_UNMOUNTED);
}
}
}
} // namespace Fm

@ -1,196 +0,0 @@
/*
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef __LIBFM2_QT_FM_FOLDER_H__
#define __LIBFM2_QT_FM_FOLDER_H__
#include <gio/gio.h>
#include <cstdint>
#include <memory>
#include <vector>
#include <unordered_map>
#include <mutex>
#include <functional>
#include <QObject>
#include <QtGlobal>
#include "../libfmqtglobals.h"
#include "gioptrs.h"
#include "fileinfo.h"
#include "job.h"
#include "volumemanager.h"
namespace Fm {
class DirListJob;
class FileSystemInfoJob;
class FileInfoJob;
class LIBFM_QT_API Folder: public QObject {
Q_OBJECT
public:
explicit Folder();
explicit Folder(const FilePath& path);
virtual ~Folder();
static std::shared_ptr<Folder> fromPath(const FilePath& path);
bool makeDirectory(const char* name, GError** error);
void queryFilesystemInfo();
bool getFilesystemInfo(uint64_t* total_size, uint64_t* free_size) const;
void reload();
bool isIncremental() const;
bool isValid() const;
bool isLoaded() const;
std::shared_ptr<const FileInfo> fileByName(const char* name) const;
bool isEmpty() const;
FileInfoList files() const;
const FilePath& path() const;
const std::shared_ptr<const FileInfo> &info() const;
bool hadCutFilesUnset();
bool hasCutFiles();
void setCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet);
void forEachFile(std::function<void (const std::shared_ptr<const FileInfo>&)> func) const {
std::lock_guard<std::mutex> lock{mutex_};
for(auto it = files_.begin(); it != files_.end(); ++it) {
func(it->second);
}
}
Q_SIGNALS:
void startLoading();
void finishLoading();
void filesAdded(FileInfoList& addedFiles);
void filesChanged(std::vector<FileInfoPair>& changePairs);
void filesRemoved(FileInfoList& removedFiles);
void removed();
void changed();
void unmount();
void contentChanged();
void fileSystemChanged();
// FIXME: this API design is bad. We leave this here to be compatible with the old libfm C API.
// It might be better to remember the error state while loading the folder, and let the user of the
// API handle the error on finish.
void error(const GErrorPtr& err, Job::ErrorSeverity severity, Job::ErrorAction& response);
private:
static void _onFileChangeEvents(GFileMonitor* monitor, GFile* file, GFile* other_file, GFileMonitorEvent event_type, Folder* _this) {
_this->onFileChangeEvents(monitor, file, other_file, event_type);
}
void onFileChangeEvents(GFileMonitor* monitor, GFile* file, GFile* other_file, GFileMonitorEvent event_type);
void onDirChanged(GFileMonitorEvent event_type);
void queueUpdate();
void queueReload();
bool eventFileAdded(const FilePath &path);
bool eventFileChanged(const FilePath &path);
void eventFileDeleted(const FilePath &path);
private Q_SLOTS:
void processPendingChanges();
void onDirListFinished();
void onFileSystemInfoFinished();
void onFileInfoFinished();
void onIdleReload();
void onMountAdded(const Mount& mnt);
void onMountRemoved(const Mount& mnt);
private:
FilePath dirPath_;
GFileMonitorPtr dirMonitor_;
std::shared_ptr<const FileInfo> dirInfo_;
DirListJob* dirlist_job;
std::vector<FileInfoJob*> fileinfoJobs_;
FileSystemInfoJob* fsInfoJob_;
std::shared_ptr<VolumeManager> volumeManager_;
/* for file monitor */
bool has_idle_reload_handler;
bool has_idle_update_handler;
std::vector<FilePath> paths_to_add;
std::vector<FilePath> paths_to_update;
std::vector<FilePath> paths_to_del;
// GSList* pending_jobs;
bool pending_change_notify;
bool filesystem_info_pending;
bool wants_incremental;
bool stop_emission; /* don't set it 1 bit to not lock other bits */
std::unordered_map<const std::string, std::shared_ptr<const FileInfo>, std::hash<std::string>> files_;
/* filesystem info - set in query thread, read in main */
uint64_t fs_total_size;
uint64_t fs_free_size;
GCancellablePtr fs_size_cancellable;
bool has_fs_info : 1;
bool defer_content_test : 1;
static std::unordered_map<FilePath, std::weak_ptr<Folder>, FilePathHash> cache_;
static QString cutFilesDirPath_;
static QString lastCutFilesDirPath_;
static std::shared_ptr<const HashSet> cutFilesHashSet_;
static std::mutex mutex_;
};
}
#endif // __LIBFM_QT_FM2_FOLDER_H__

@ -1,137 +0,0 @@
#ifndef GIOPTRS_H
#define GIOPTRS_H
// define smart pointers for GIO data types
#include <glib.h>
#include <gio/gio.h>
#include "gobjectptr.h"
#include "cstrptr.h"
namespace Fm {
typedef GObjectPtr<GFile> GFilePtr;
typedef GObjectPtr<GFileInfo> GFileInfoPtr;
typedef GObjectPtr<GFileMonitor> GFileMonitorPtr;
typedef GObjectPtr<GCancellable> GCancellablePtr;
typedef GObjectPtr<GFileEnumerator> GFileEnumeratorPtr;
typedef GObjectPtr<GInputStream> GInputStreamPtr;
typedef GObjectPtr<GFileInputStream> GFileInputStreamPtr;
typedef GObjectPtr<GOutputStream> GOutputStreamPtr;
typedef GObjectPtr<GFileOutputStream> GFileOutputStreamPtr;
typedef GObjectPtr<GIcon> GIconPtr;
typedef GObjectPtr<GVolumeMonitor> GVolumeMonitorPtr;
typedef GObjectPtr<GVolume> GVolumePtr;
typedef GObjectPtr<GMount> GMountPtr;
typedef GObjectPtr<GAppInfo> GAppInfoPtr;
class GErrorPtr {
public:
GErrorPtr(): err_{nullptr} {
}
GErrorPtr(GError*&& err) noexcept: err_{err} {
err = nullptr;
}
GErrorPtr(const GErrorPtr& other) = delete;
GErrorPtr(GErrorPtr&& other) noexcept: err_{other.err_} {
other.err_ = nullptr;
}
GErrorPtr(std::uint32_t domain, unsigned int code, const char* msg):
GErrorPtr{g_error_new_literal(domain, code, msg)} {
}
GErrorPtr(std::uint32_t domain, unsigned int code, const QString& msg):
GErrorPtr{domain, code, msg.toUtf8().constData()} {
}
~GErrorPtr() {
reset();
}
std::uint32_t domain() const {
if(err_ != nullptr) {
return err_->domain;
}
return 0;
}
unsigned int code() const {
if(err_ != nullptr) {
return err_->code;
}
return 0;
}
QString message() const {
if(err_ != nullptr) {
return err_->message;
}
return QString();
}
void reset() {
if(err_) {
g_error_free(err_);
}
err_ = nullptr;
}
GError* get() const {
return err_;
}
GErrorPtr& operator = (const GErrorPtr& other) = delete;
GErrorPtr& operator = (GErrorPtr&& other) noexcept {
reset();
err_ = other.err_;
other.err_ = nullptr;
return *this;
}
GErrorPtr& operator = (GError*&& err) {
reset();
err_ = err;
err_ = nullptr;
return *this;
}
GError** operator&() {
return &err_;
}
GError* operator->() {
return err_;
}
bool operator == (const GErrorPtr& other) const {
return err_ == other.err_;
}
bool operator == (GError* err) const {
return err_ == err;
}
bool operator != (std::nullptr_t) const {
return err_ != nullptr;
}
operator bool() const {
return err_ != nullptr;
}
private:
GError* err_;
};
} //namespace Fm
#endif // GIOPTRS_H

@ -1,104 +0,0 @@
#ifndef FM2_GOBJECTPTR_H
#define FM2_GOBJECTPTR_H
#include "../libfmqtglobals.h"
#include <glib.h>
#include <glib-object.h>
#include <cstddef>
#include <QDebug>
namespace Fm {
template <typename T>
class LIBFM_QT_API GObjectPtr {
public:
explicit GObjectPtr(): gobj_{nullptr} {
}
explicit GObjectPtr(T* gobj, bool add_ref = true): gobj_{gobj} {
if(gobj_ != nullptr && add_ref)
g_object_ref(gobj_);
}
GObjectPtr(const GObjectPtr& other): gobj_{other.gobj_ ? reinterpret_cast<T*>(g_object_ref(other.gobj_)) : nullptr} {
}
GObjectPtr(GObjectPtr&& other) noexcept: gobj_{other.release()} {
}
~GObjectPtr() {
if(gobj_ != nullptr)
g_object_unref(gobj_);
}
T* get() const {
return gobj_;
}
T* release() {
T* tmp = gobj_;
gobj_ = nullptr;
return tmp;
}
void reset() {
if(gobj_ != nullptr)
g_object_unref(gobj_);
gobj_ = nullptr;
}
GObjectPtr& operator = (const GObjectPtr& other) {
if (*this == other)
return *this;
if(gobj_ != nullptr)
g_object_unref(gobj_);
gobj_ = other.gobj_ ? reinterpret_cast<T*>(g_object_ref(other.gobj_)) : nullptr;
return *this;
}
GObjectPtr& operator = (GObjectPtr&& other) noexcept {
if (this == &other)
return *this;
if(gobj_ != nullptr)
g_object_unref(gobj_);
gobj_ = other.release();
return *this;
}
GObjectPtr& operator = (T* gobj) {
if (*this == gobj)
return *this;
if(gobj_ != nullptr)
g_object_unref(gobj_);
gobj_ = gobj ? reinterpret_cast<T*>(g_object_ref(gobj_)) : nullptr;
return *this;
}
bool operator == (const GObjectPtr& other) const {
return gobj_ == other.gobj_;
}
bool operator == (T* gobj) const {
return gobj_ == gobj;
}
bool operator != (std::nullptr_t) const {
return gobj_ != nullptr;
}
operator bool() const {
return gobj_ != nullptr;
}
private:
mutable T* gobj_;
};
} // namespace Fm
#endif // FM2_GOBJECTPTR_H

@ -1,147 +0,0 @@
#include "iconinfo.h"
#include "iconinfo_p.h"
namespace Fm {
std::unordered_map<GIcon*, std::shared_ptr<IconInfo>, IconInfo::GIconHash, IconInfo::GIconEqual> IconInfo::cache_;
std::mutex IconInfo::mutex_;
QList<QIcon> IconInfo::fallbackQicons_;
static const char* fallbackIconNames[] = {
"unknown",
"application-octet-stream",
"application-x-generic",
"text-x-generic",
nullptr
};
static QIcon getFirst(const QList<QIcon> & icons)
{
for (const auto & icon : icons) {
if (!icon.isNull())
return icon;
}
return QIcon{};
}
IconInfo::IconInfo(const char* name):
gicon_{g_themed_icon_new(name), false} {
}
IconInfo::IconInfo(const GIconPtr gicon):
gicon_{std::move(gicon)} {
}
IconInfo::~IconInfo() {
}
// static
std::shared_ptr<const IconInfo> IconInfo::fromName(const char* name) {
GObjectPtr<GIcon> gicon{g_themed_icon_new(name), false};
return fromGIcon(gicon);
}
// static
std::shared_ptr<const IconInfo> IconInfo::fromGIcon(GIconPtr gicon) {
if(Q_LIKELY(gicon)) {
std::lock_guard<std::mutex> lock{mutex_};
auto it = cache_.find(gicon.get());
if(it != cache_.end()) {
return it->second;
}
// not found in the cache, create a new entry for it.
auto icon = std::make_shared<IconInfo>(std::move(gicon));
cache_.insert(std::make_pair(icon->gicon_.get(), icon));
return icon;
}
return std::shared_ptr<const IconInfo>{};
}
void IconInfo::updateQIcons() {
std::lock_guard<std::mutex> lock{mutex_};
for(auto& elem: cache_) {
auto& info = elem.second;
info->internalQicons_.clear();
}
}
QIcon IconInfo::qicon(const bool& transparent) const {
if(Q_LIKELY(!transparent)) {
if(Q_UNLIKELY(qicon_.isNull() && gicon_)) {
if(!G_IS_FILE_ICON(gicon_.get())) {
qicon_ = QIcon(new IconEngine{shared_from_this()});
}
else {
qicon_ = getFirst(internalQicons_);
}
}
}
else { // transparent == true
if(Q_UNLIKELY(qiconTransparent_.isNull() && gicon_)) {
if(!G_IS_FILE_ICON(gicon_.get())) {
qiconTransparent_ = QIcon(new IconEngine{shared_from_this(), transparent});
}
else {
qiconTransparent_ = getFirst(internalQicons_);
}
}
}
return !transparent ? qicon_ : qiconTransparent_;
}
QList<QIcon> IconInfo::qiconsFromNames(const char* const* names) {
QList<QIcon> icons;
// qDebug("names: %p", names);
for(const gchar* const* name = names; *name; ++name) {
// qDebug("icon name=%s", *name);
icons.push_back(QIcon::fromTheme(*name));
}
return icons;
}
std::forward_list<std::shared_ptr<const IconInfo>> IconInfo::emblems() const {
std::forward_list<std::shared_ptr<const IconInfo>> result;
if(hasEmblems()) {
const GList* emblems_glist = g_emblemed_icon_get_emblems(G_EMBLEMED_ICON(gicon_.get()));
for(auto l = emblems_glist; l; l = l->next) {
auto gemblem = G_EMBLEM(l->data);
GIconPtr gemblem_icon{g_emblem_get_icon(gemblem), true};
result.emplace_front(fromGIcon(gemblem_icon));
}
result.reverse();
}
return result;
}
QIcon IconInfo::internalQicon() const {
QIcon ret_icon;
if(Q_UNLIKELY(internalQicons_.isEmpty())) {
GIcon* gicon = gicon_.get();
if(G_IS_EMBLEMED_ICON(gicon_.get())) {
gicon = g_emblemed_icon_get_icon(G_EMBLEMED_ICON(gicon));
}
if(G_IS_THEMED_ICON(gicon)) {
const gchar* const* names = g_themed_icon_get_names(G_THEMED_ICON(gicon));
internalQicons_ = qiconsFromNames(names);
}
else if(G_IS_FILE_ICON(gicon)) {
GFile* file = g_file_icon_get_file(G_FILE_ICON(gicon));
CStrPtr fpath{g_file_get_path(file)};
internalQicons_.push_back(QIcon(fpath.get()));
}
}
ret_icon = getFirst(internalQicons_);
// fallback to default icon
if(Q_UNLIKELY(ret_icon.isNull())) {
if(Q_UNLIKELY(fallbackQicons_.isEmpty())) {
fallbackQicons_ = qiconsFromNames(fallbackIconNames);
}
ret_icon = getFirst(fallbackQicons_);
}
return ret_icon;
}
} // namespace Fm

@ -1,112 +0,0 @@
/*
* fm-icon.h
*
* Copyright 2009 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
* Copyright 2013 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
*
* This file is a part of the Libfm library.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __FM2_ICON_INFO_H__
#define __FM2_ICON_INFO_H__
#include "../libfmqtglobals.h"
#include <gio/gio.h>
#include "gioptrs.h"
#include <memory>
#include <mutex>
#include <unordered_map>
#include <forward_list>
#include <QIcon>
namespace Fm {
class LIBFM_QT_API IconInfo: public std::enable_shared_from_this<IconInfo> {
public:
friend class IconEngine;
explicit IconInfo() {}
explicit IconInfo(const char* name);
explicit IconInfo(const GIconPtr gicon);
~IconInfo();
static std::shared_ptr<const IconInfo> fromName(const char* name);
static std::shared_ptr<const IconInfo> fromGIcon(GIconPtr gicon);
static std::shared_ptr<const IconInfo> fromGIcon(GIcon* gicon) {
return fromGIcon(GIconPtr{gicon, true});
}
static void updateQIcons();
GIconPtr gicon() const {
return gicon_;
}
QIcon qicon(const bool& transparent = false) const;
bool hasEmblems() const {
return G_IS_EMBLEMED_ICON(gicon_.get());
}
std::forward_list<std::shared_ptr<const IconInfo>> emblems() const;
bool isValid() const {
return gicon_ != nullptr;
}
private:
static QList<QIcon> qiconsFromNames(const char* const* names);
// actual QIcon loaded by QIcon::fromTheme
QIcon internalQicon() const;
struct GIconHash {
std::size_t operator()(GIcon* gicon) const {
return g_icon_hash(gicon);
}
};
struct GIconEqual {
bool operator()(GIcon* gicon1, GIcon* gicon2) const {
return g_icon_equal(gicon1, gicon2);
}
};
private:
GIconPtr gicon_;
mutable QIcon qicon_;
mutable QIcon qiconTransparent_;
mutable QList<QIcon> internalQicons_;
static std::unordered_map<GIcon*, std::shared_ptr<IconInfo>, GIconHash, GIconEqual> cache_;
static std::mutex mutex_;
static QList<QIcon> fallbackQicons_;
};
} // namespace Fm
Q_DECLARE_METATYPE(std::shared_ptr<const Fm::IconInfo>)
#endif /* __FM2_ICON_INFO_H__ */

@ -1,126 +0,0 @@
/*
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef FM_ICONENGINE_H
#define FM_ICONENGINE_H
#include <QIconEngine>
#include <QPainter>
#include "../libfmqtglobals.h"
#include "iconinfo.h"
#include <gio/gio.h>
namespace Fm {
class IconEngine: public QIconEngine {
public:
IconEngine(std::shared_ptr<const Fm::IconInfo> info, const bool& transparent = false);
~IconEngine();
virtual QSize actualSize(const QSize& size, QIcon::Mode mode, QIcon::State state) override;
// not supported
virtual void addFile(const QString& /*fileName*/, const QSize& /*size*/, QIcon::Mode /*mode*/, QIcon::State /*state*/) override {}
// not supported
virtual void addPixmap(const QPixmap& /*pixmap*/, QIcon::Mode /*mode*/, QIcon::State /*state*/) override {}
virtual QIconEngine* clone() const override;
virtual QString key() const override;
virtual void paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) override;
virtual QPixmap pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state) override;
virtual void virtual_hook(int id, void* data) override;
private:
std::weak_ptr<const Fm::IconInfo> info_;
bool transparent_;
};
IconEngine::IconEngine(std::shared_ptr<const IconInfo> info, const bool& transparent):
info_{info}, transparent_{transparent} {
}
IconEngine::~IconEngine() {
}
QSize IconEngine::actualSize(const QSize& size, QIcon::Mode mode, QIcon::State state) {
auto info = info_.lock();
return info ? info->internalQicon().actualSize(size, mode, state) : QSize{};
}
QIconEngine* IconEngine::clone() const {
IconEngine* engine = new IconEngine(info_.lock());
return engine;
}
QString IconEngine::key() const {
return QStringLiteral("Fm::IconEngine");
}
void IconEngine::paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) {
auto info = info_.lock();
if(info) {
if(transparent_) {
painter->save();
painter->setOpacity(0.45);
}
info->internalQicon().paint(painter, rect, Qt::AlignCenter, mode, state);
if(transparent_) {
painter->restore();
}
}
}
QPixmap IconEngine::pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state) {
auto info = info_.lock();
return info ? info->internalQicon().pixmap(size, mode, state) : QPixmap{};
}
void IconEngine::virtual_hook(int id, void* data) {
auto info = info_.lock();
switch(id) {
case QIconEngine::AvailableSizesHook: {
auto* args = reinterpret_cast<QIconEngine::AvailableSizesArgument*>(data);
args->sizes = info ? info->internalQicon().availableSizes(args->mode, args->state) : QList<QSize>{};
break;
}
case QIconEngine::IconNameHook: {
QString* result = reinterpret_cast<QString*>(data);
*result = info ? info->internalQicon().name() : QString{};
break;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
case QIconEngine::IsNullHook: {
bool* result = reinterpret_cast<bool*>(data);
*result = info ? info->internalQicon().isNull() : true;
break;
}
#endif
}
}
} // namespace Fm
#endif // FM_ICONENGINE_H

@ -1,57 +0,0 @@
#include "job.h"
#include "job_p.h"
namespace Fm {
Job::Job():
paused_{false},
cancellable_{g_cancellable_new(), false},
cancellableHandler_{g_signal_connect(cancellable_.get(), "cancelled", G_CALLBACK(_onCancellableCancelled), this)} {
}
Job::~Job() {
if(cancellable_) {
g_cancellable_disconnect(cancellable_.get(), cancellableHandler_);
}
}
void Job::runAsync(QThread::Priority priority) {
auto thread = new JobThread(this);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
if(autoDelete()) {
connect(this, &Job::finished, this, &Job::deleteLater);
}
thread->start(priority);
}
void Job::cancel() {
g_cancellable_cancel(cancellable_.get());
}
void Job::run() {
exec();
Q_EMIT finished();
}
Job::ErrorAction Job::emitError(const GErrorPtr &err, Job::ErrorSeverity severity) {
ErrorAction response = ErrorAction::CONTINUE;
// if the error is already handled, don't emit it.
if(err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_FAILED_HANDLED) {
return response;
}
Q_EMIT error(err, severity, response);
if(severity == ErrorSeverity::CRITICAL || response == ErrorAction::ABORT) {
cancel();
}
else if(response == ErrorAction::RETRY ) {
/* If the job is already cancelled, retry is not allowed. */
if(isCancelled() || (err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_CANCELLED)) {
response = ErrorAction::CONTINUE;
}
}
return response;
}
} // namespace Fm

@ -1,120 +0,0 @@
/*
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef __LIBFM_QT_FM_JOB_H__
#define __LIBFM_QT_FM_JOB_H__
#include <libfm/fm.h>
#include <QObject>
#include <QtGlobal>
#include <QThread>
#include <QRunnable>
#include <memory>
#include <gio/gio.h>
#include "gobjectptr.h"
#include "gioptrs.h"
#include "../libfmqtglobals.h"
namespace Fm {
/*
* Fm::Job can be used in several different modes.
* 1. run with QThreadPool::start()
* 2. call runAsync(), which will create a new QThread and move the object to the thread.
* 3. create a new QThread, and connect the started() signal to the slot Job::run()
* 4. Directly call Job::run(), which executes synchrounously as a normal blocking call
*/
class LIBFM_QT_API Job: public QObject, public QRunnable {
Q_OBJECT
public:
enum class ErrorAction{
CONTINUE,
RETRY,
ABORT
};
enum class ErrorSeverity {
UNKNOWN,
WARNING,
MILD,
MODERATE,
SEVERE,
CRITICAL
};
explicit Job();
virtual ~Job();
bool isCancelled() const {
return g_cancellable_is_cancelled(cancellable_.get());
}
void runAsync(QThread::Priority priority = QThread::InheritPriority);
bool pause();
void resume();
const GCancellablePtr& cancellable() const {
return cancellable_;
}
Q_SIGNALS:
void cancelled();
void finished();
// this signal should be connected with Qt::BlockingQueuedConnection
void error(const GErrorPtr& err, ErrorSeverity severity, ErrorAction& response);
public Q_SLOTS:
void cancel();
void run() override;
protected:
ErrorAction emitError(const GErrorPtr& err, ErrorSeverity severity = ErrorSeverity::MODERATE);
// all derived job subclasses should do their work in this method.
virtual void exec() = 0;
private:
static void _onCancellableCancelled(GCancellable* cancellable, Job* _this) {
_this->onCancellableCancelled(cancellable);
}
void onCancellableCancelled(GCancellable* /*cancellable*/) {
Q_EMIT cancelled();
}
private:
bool paused_;
GCancellablePtr cancellable_;
gulong cancellableHandler_;
};
}
#endif // __LIBFM_QT_FM_JOB_H__

@ -1,26 +0,0 @@
#ifndef JOB_P_H
#define JOB_P_H
#include <QThread>
#include "job.h"
namespace Fm {
class JobThread: public QThread {
Q_OBJECT
public:
JobThread(Job* job): job_{job} {
}
protected:
void run() override {
job_->run();
}
Job* job_;
};
} // namespace Fm
#endif // JOB_P_H

@ -1,64 +0,0 @@
#include "mimetype.h"
#include <cstring>
#include <glib.h>
#include <gio/gio.h>
using namespace std;
namespace Fm {
std::unordered_map<const char*, std::shared_ptr<const MimeType>, CStrHash, CStrEqual> MimeType::cache_;
std::mutex MimeType::mutex_;
std::shared_ptr<const MimeType> MimeType::inodeDirectory_; // inode/directory
std::shared_ptr<const MimeType> MimeType::inodeShortcut_; // inode/x-shortcut
std::shared_ptr<const MimeType> MimeType::inodeMountPoint_; // inode/mount-point
std::shared_ptr<const MimeType> MimeType::desktopEntry_; // application/x-desktop
MimeType::MimeType(const char* typeName):
name_{g_strdup(typeName)},
desc_{nullptr} {
GObjectPtr<GIcon> gicon{g_content_type_get_icon(typeName), false};
if(strcmp(typeName, "inode/directory") == 0)
g_themed_icon_prepend_name(G_THEMED_ICON(gicon.get()), "folder");
else if(g_content_type_can_be_executable(typeName))
g_themed_icon_append_name(G_THEMED_ICON(gicon.get()), "application-x-executable");
icon_ = IconInfo::fromGIcon(gicon);
}
MimeType::~MimeType () {
}
//static
std::shared_ptr<const MimeType> MimeType::fromName(const char* typeName) {
std::shared_ptr<const MimeType> ret;
std::lock_guard<std::mutex> lock(mutex_);
auto it = cache_.find(typeName);
if(it == cache_.end()) {
ret = std::make_shared<MimeType>(typeName);
cache_.insert(std::make_pair(ret->name_.get(), ret));
}
else {
ret = it->second;
}
return ret;
}
// static
std::shared_ptr<const MimeType> MimeType::guessFromFileName(const char* fileName) {
gboolean uncertain;
/* let skip scheme and host from non-native names */
auto uri_scheme = g_strstr_len(fileName, -1, "://");
if(uri_scheme)
fileName = strchr(uri_scheme + 3, '/');
if(fileName == nullptr)
fileName = "unknown";
auto type = CStrPtr{g_content_type_guess(fileName, nullptr, 0, &uncertain)};
return fromName(type.get());
}
} // namespace Fm

@ -1,172 +0,0 @@
/*
* fm-mime-type.h
*
* Copyright 2009 - 2012 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This file is a part of the Libfm library.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _FM2_MIME_TYPE_H_
#define _FM2_MIME_TYPE_H_
#include "../libfmqtglobals.h"
#include <glib.h>
#include <gio/gio.h>
#include <memory>
#include <vector>
#include <string>
#include <mutex>
#include <cstring>
#include <forward_list>
#include <functional>
#include "cstrptr.h"
#include "gobjectptr.h"
#include "iconinfo.h"
#include "thumbnailer.h"
namespace Fm {
class LIBFM_QT_API MimeType {
public:
friend class Thumbnailer;
explicit MimeType(const char* typeName);
MimeType() = delete;
~MimeType();
std::shared_ptr<const Thumbnailer> firstThumbnailer() const {
std::lock_guard<std::mutex> lock{mutex_};
return thumbnailers_.empty() ? nullptr : thumbnailers_.front();
}
void forEachThumbnailer(std::function<bool(const std::shared_ptr<const Thumbnailer>&)> func) const {
std::lock_guard<std::mutex> lock{mutex_};
for(auto& thumbnailer: thumbnailers_) {
if(func(thumbnailer)) {
break;
}
}
}
const std::shared_ptr<const IconInfo>& icon() const {
return icon_;
}
const char* name() const {
return name_.get();
}
const char* desc() const {
if(!desc_) {
desc_ = CStrPtr{g_content_type_get_description(name_.get())};
}
return desc_.get();
}
static std::shared_ptr<const MimeType> fromName(const char* typeName);
static std::shared_ptr<const MimeType> guessFromFileName(const char* fileName);
bool isUnknownType() const {
return g_content_type_is_unknown(name_.get());
}
bool isDesktopEntry() const {
return this == desktopEntry().get();
}
bool isText() const {
return g_content_type_is_a(name_.get(), "text/plain");
}
bool isImage() const {
return !std::strncmp("image/", name_.get(), 6);
}
bool isMountable() const {
return this == inodeMountPoint().get();
}
bool isShortcut() const {
return this == inodeShortcut().get();
}
bool isDir() const {
return this == inodeDirectory().get();
}
bool canBeExecutable() const {
return g_content_type_can_be_executable(name_.get());
}
static std::shared_ptr<const MimeType> inodeDirectory() { // inode/directory
if(!inodeDirectory_)
inodeDirectory_ = fromName("inode/directory");
return inodeDirectory_;
}
static std::shared_ptr<const MimeType> inodeShortcut() { // inode/x-shortcut
if(!inodeShortcut_)
inodeShortcut_ = fromName("inode/x-shortcut");
return inodeShortcut_;
}
static std::shared_ptr<const MimeType> inodeMountPoint() { // inode/mount-point
if(!inodeMountPoint_)
inodeMountPoint_ = fromName("inode/mount-point");
return inodeMountPoint_;
}
static std::shared_ptr<const MimeType> desktopEntry() { // application/x-desktop
if(!desktopEntry_)
desktopEntry_ = fromName("application/x-desktop");
return desktopEntry_;
}
private:
void removeThumbnailer(std::shared_ptr<const Thumbnailer>& thumbnailer) {
std::lock_guard<std::mutex> lock{mutex_};
thumbnailers_.remove(thumbnailer);
}
void addThumbnailer(std::shared_ptr<const Thumbnailer> thumbnailer) {
std::lock_guard<std::mutex> lock{mutex_};
thumbnailers_.push_front(std::move(thumbnailer));
}
private:
std::shared_ptr<const IconInfo> icon_;
CStrPtr name_;
mutable CStrPtr desc_;
std::forward_list<std::shared_ptr<const Thumbnailer>> thumbnailers_;
static std::unordered_map<const char*, std::shared_ptr<const MimeType>, CStrHash, CStrEqual> cache_;
static std::mutex mutex_;
static std::shared_ptr<const MimeType> inodeDirectory_; // inode/directory
static std::shared_ptr<const MimeType> inodeShortcut_; // inode/x-shortcut
static std::shared_ptr<const MimeType> inodeMountPoint_; // inode/mount-point
static std::shared_ptr<const MimeType> desktopEntry_; // application/x-desktop
};
} // namespace Fm
#endif

@ -1,130 +0,0 @@
#include "templates.h"
#include "gioptrs.h"
#include <algorithm>
#include <QDebug>
using namespace std;
namespace Fm {
std::weak_ptr<Templates> Templates::globalInstance_;
TemplateItem::TemplateItem(std::shared_ptr<const FileInfo> file): fileInfo_{file} {
}
FilePath TemplateItem::filePath() const {
auto& target = fileInfo_->target();
if(fileInfo_->isDesktopEntry() && !target.empty()) {
if(target[0] == '/') { // target is an absolute path
return FilePath::fromLocalPath(target.c_str());
}
else { // resolve relative path
return fileInfo_->dirPath().relativePath(target.c_str());
}
}
return fileInfo_->path();
}
Templates::Templates() : QObject() {
auto* data_dirs = g_get_system_data_dirs();
// system-wide template dirs
for(auto data_dir = data_dirs; *data_dir; ++data_dir) {
CStrPtr dir_name{g_build_filename(*data_dir, "templates", nullptr)};
addTemplateDir(dir_name.get());
}
// user-specific template dir
CStrPtr dir_name{g_build_filename(g_get_user_data_dir(), "templates", nullptr)};
addTemplateDir(dir_name.get());
// $XDG_TEMPLATES_DIR (FIXME: this might change at runtime)
const gchar *special_dir = g_get_user_special_dir(G_USER_DIRECTORY_TEMPLATES);
if (special_dir) {
addTemplateDir(special_dir);
}
}
shared_ptr<Templates> Templates::globalInstance() {
auto templates = globalInstance_.lock();
if(!templates) {
templates = make_shared<Templates>();
globalInstance_ = templates;
}
return templates;
}
void Templates::addTemplateDir(const char* dirPathName) {
auto dir_path = FilePath::fromLocalPath(dirPathName);
if(dir_path.isValid()) {
auto folder = Folder::fromPath(dir_path);
connect(folder.get(), &Folder::filesAdded, this, &Templates::onFilesAdded);
connect(folder.get(), &Folder::filesChanged, this, &Templates::onFilesChanged);
connect(folder.get(), &Folder::filesRemoved, this, &Templates::onFilesRemoved);
connect(folder.get(), &Folder::removed, this, &Templates::onTemplateDirRemoved);
templateFolders_.emplace_back(std::move(folder));
}
}
void Templates::onFilesAdded(FileInfoList& addedFiles) {
for(auto& file : addedFiles) {
// FIXME: we do not support subdirs right now (only XFCE supports this)
if(file->isHidden() || file->isDir()) {
continue;
}
items_.emplace_back(std::make_shared<TemplateItem>(file));
// emit a signal for the addition
Q_EMIT itemAdded(items_.back());
}
}
void Templates::onFilesChanged(std::vector<FileInfoPair>& changePairs) {
for(auto& change: changePairs) {
auto& old_file = change.first;
auto& new_file = change.second;
auto it = std::find_if(items_.begin(), items_.end(), [&](const std::shared_ptr<TemplateItem>& item) {
return item->fileInfo() == old_file;
});
if(it != items_.end()) {
// emit a signal for the change
auto old = *it;
*it = std::make_shared<TemplateItem>(new_file);
Q_EMIT itemChanged(old, *it);
}
}
}
void Templates::onFilesRemoved(FileInfoList& removedFiles) {
for(auto& file : removedFiles) {
auto filePath = file->path();
auto it = std::remove_if(items_.begin(), items_.end(), [&](const std::shared_ptr<TemplateItem>& item) {
return item->fileInfo() == file;
});
for(auto removed_it = it; it != items_.end(); ++it) {
// emit a signal for the removal
Q_EMIT itemRemoved(*removed_it);
}
items_.erase(it, items_.end());
}
}
void Templates::onTemplateDirRemoved() {
// the whole template dir is removed
auto folder = static_cast<Folder*>(sender());
if(!folder) {
return;
}
auto dirPath = folder->path();
// remote all files under this dir
auto it = std::remove_if(items_.begin(), items_.end(), [&](const std::shared_ptr<TemplateItem>& item) {
return dirPath.isPrefixOf(item->filePath());
});
for(auto removed_it = it; it != items_.end(); ++it) {
// emit a signal for the removal
Q_EMIT itemRemoved(*removed_it);
}
items_.erase(it, items_.end());
}
} // namespace Fm

@ -1,100 +0,0 @@
#ifndef TEMPLATES_H
#define TEMPLATES_H
#include "../libfmqtglobals.h"
#include <QObject>
#include <memory>
#include <vector>
#include "folder.h"
#include "fileinfo.h"
#include "mimetype.h"
#include "iconinfo.h"
namespace Fm {
class LIBFM_QT_API TemplateItem {
public:
explicit TemplateItem(std::shared_ptr<const FileInfo> fileInfo);
QString displayName() const {
return fileInfo_->displayName();
}
const std::string& name() const {
return fileInfo_->name();
}
std::shared_ptr<const IconInfo> icon() const {
return fileInfo_->icon();
}
std::shared_ptr<const FileInfo> fileInfo() const {
return fileInfo_;
}
std::shared_ptr<const MimeType> mimeType() const {
return fileInfo_->mimeType();
}
FilePath filePath() const;
private:
std::shared_ptr<const FileInfo> fileInfo_;
};
class LIBFM_QT_API Templates : public QObject {
Q_OBJECT
public:
explicit Templates();
// FIXME: the first call to this method will get no templates since dir loading is in progress.
static std::shared_ptr<Templates> globalInstance();
void forEachItem(std::function<void (const std::shared_ptr<const TemplateItem>&)> func) const {
for(const auto& item : items_) {
func(item);
}
}
std::vector<std::shared_ptr<const TemplateItem>> items() const {
std::vector<std::shared_ptr<const TemplateItem>> tmp_items;
for(auto& item: items_) {
tmp_items.emplace_back(item);
}
return tmp_items;
}
bool hasTemplates() const {
return !items_.empty();
}
Q_SIGNALS:
void itemAdded(const std::shared_ptr<const TemplateItem>& item);
void itemChanged(const std::shared_ptr<const TemplateItem>& oldItem, const std::shared_ptr<const TemplateItem>& newItem);
void itemRemoved(const std::shared_ptr<const TemplateItem>& item);
private:
void addTemplateDir(const char* dirPathName);
private Q_SLOTS:
void onFilesAdded(FileInfoList& addedFiles);
void onFilesChanged(std::vector<FileInfoPair>& changePairs);
void onFilesRemoved(FileInfoList& removedFiles);
void onTemplateDirRemoved();
private:
std::vector<std::shared_ptr<TemplateItem>> items_;
std::vector<std::shared_ptr<Folder>> templateFolders_;
static std::weak_ptr<Templates> globalInstance_;
};
} // namespace Fm
#endif // TEMPLATES_H

@ -1,127 +0,0 @@
#include "terminal.h"
namespace Fm {
#include <glib.h>
#include <gio/gdesktopappinfo.h>
#include <string.h>
#include <unistd.h>
#if !GLIB_CHECK_VERSION(2, 28, 0) && !HAVE_DECL_ENVIRON
extern char** environ;
#endif
static void child_setup(gpointer user_data) {
/* Move child to grandparent group so it will not die with parent */
setpgid(0, (pid_t)(gsize)user_data);
}
bool launchTerminal(const char* programName, const FilePath& workingDir, Fm::GErrorPtr& error) {
/* read system terminals file */
GKeyFile* kf = g_key_file_new();
if(!g_key_file_load_from_file(kf, LIBFM_QT_DATA_DIR "/terminals.list", G_KEY_FILE_NONE, &error)) {
g_key_file_free(kf);
return false;
}
auto launch = g_key_file_get_string(kf, programName, "launch", nullptr);
auto desktop_id = g_key_file_get_string(kf, programName, "desktop_id", nullptr);
GDesktopAppInfo* appinfo = nullptr;
if(desktop_id) {
appinfo = g_desktop_app_info_new(desktop_id);
}
const gchar* cmd;
gchar* _cmd = nullptr;
if(appinfo) {
cmd = g_app_info_get_commandline(G_APP_INFO(appinfo));
}
else if(launch) {
cmd = _cmd = g_strdup_printf("%s %s", programName, launch);
}
else {
cmd = programName;
}
#if 0 // FIXME: what's this?
if(custom_args) {
cmd = g_strdup_printf("%s %s", cmd, custom_args);
g_free(_cmd);
_cmd = (char*)cmd;
}
#endif
char** argv;
int argc;
if(!g_shell_parse_argv(cmd, &argc, &argv, nullptr)) {
argv = nullptr;
}
g_free(_cmd);
if(appinfo) {
g_object_unref(appinfo);
}
if(!argv) { /* parsing failed */
return false;
}
char** envp;
#if GLIB_CHECK_VERSION(2, 28, 0)
envp = g_get_environ();
#else
envp = g_strdupv(environ);
#endif
auto dir = workingDir ? workingDir.localPath() : nullptr;
if(dir) {
#if GLIB_CHECK_VERSION(2, 32, 0)
envp = g_environ_setenv(envp, "PWD", dir.get(), TRUE);
#else
char** env = envp;
if(env) while(*env != nullptr) {
if(strncmp(*env, "PWD=", 4) == 0) {
break;
}
env++;
}
if(env == nullptr || *env == nullptr) {
gint length;
length = envp ? g_strv_length(envp) : 0;
envp = g_renew(gchar*, envp, length + 2);
env = &envp[length];
env[1] = nullptr;
}
else {
g_free(*env);
}
*env = g_strdup_printf("PWD=%s", dir);
#endif
}
bool ret = g_spawn_async(dir.get(), argv, envp, G_SPAWN_SEARCH_PATH,
child_setup, (gpointer)(gsize)getpgid(getppid()),
nullptr, &error);
g_strfreev(argv);
g_strfreev(envp);
g_key_file_free(kf);
return ret;
}
std::vector<CStrPtr> allKnownTerminals() {
std::vector<CStrPtr> terminals;
GKeyFile* kf = g_key_file_new();
if(g_key_file_load_from_file(kf, LIBFM_QT_DATA_DIR "/terminals.list", G_KEY_FILE_NONE, nullptr)) {
gsize n;
auto programs = g_key_file_get_groups(kf, &n);
terminals.reserve(n);
for(auto name = programs; *name; ++name) {
terminals.emplace_back(*name);
}
g_free(programs);
}
g_key_file_free(kf);
return terminals;
}
} // namespace Fm

@ -1,17 +0,0 @@
#ifndef TERMINAL_H
#define TERMINAL_H
#include "../libfmqtglobals.h"
#include "gioptrs.h"
#include "filepath.h"
#include <vector>
namespace Fm {
LIBFM_QT_API bool launchTerminal(const char* programName, const FilePath& workingDir, GErrorPtr& error);
LIBFM_QT_API std::vector<CStrPtr> allKnownTerminals();
} // namespace Fm
#endif // TERMINAL_H

@ -1,141 +0,0 @@
#include "thumbnailer.h"
#include "mimetype.h"
#include <string>
#include <QDebug>
#include <sys/stat.h>
#include <sys/types.h>
namespace Fm {
std::mutex Thumbnailer::mutex_;
std::vector<std::shared_ptr<Thumbnailer>> Thumbnailer::allThumbnailers_;
Thumbnailer::Thumbnailer(const char* id, GKeyFile* kf):
id_{g_strdup(id)},
try_exec_{g_key_file_get_string(kf, "Thumbnailer Entry", "TryExec", nullptr)},
exec_{g_key_file_get_string(kf, "Thumbnailer Entry", "Exec", nullptr)} {
}
CStrPtr Thumbnailer::commandForUri(const char* uri, const char* output_file, guint size) const {
if(exec_) {
/* FIXME: how to handle TryExec? */
/* parse the command line and do required substitutions according to:
* https://developer.gnome.org/integration-guide/stable/thumbnailer.html.en
*/
GString* cmd_line = g_string_sized_new(1024);
const char* p;
for(p = exec_.get(); *p; ++p) {
if(G_LIKELY(*p != '%')) {
g_string_append_c(cmd_line, *p);
}
else {
char* quoted;
++p;
switch(*p) {
case '\0':
break;
case 's':
g_string_append_printf(cmd_line, "%d", size);
break;
case 'i': {
char* src_path = g_filename_from_uri(uri, nullptr, nullptr);
if(src_path) {
quoted = g_shell_quote(src_path);
g_string_append(cmd_line, quoted);
g_free(quoted);
g_free(src_path);
}
break;
}
case 'u':
quoted = g_shell_quote(uri);
g_string_append(cmd_line, quoted);
g_free(quoted);
break;
case 'o':
g_string_append(cmd_line, output_file);
break;
default:
g_string_append_c(cmd_line, '%');
if(*p != '%') {
g_string_append_c(cmd_line, *p);
}
}
}
}
return CStrPtr{g_string_free(cmd_line, FALSE)};
}
return nullptr;
}
bool Thumbnailer::run(const char* uri, const char* output_file, int size) const {
auto cmd = commandForUri(uri, output_file, size);
qDebug() << cmd.get();
int status;
bool ret = g_spawn_command_line_sync(cmd.get(), nullptr, nullptr, &status, nullptr);
return ret && status == 0;
}
static void find_thumbnailers_in_data_dir(std::unordered_map<std::string, const char*>& hash, const char* data_dir) {
CStrPtr dir_path{g_build_filename(data_dir, "thumbnailers", nullptr)};
GDir* dir = g_dir_open(dir_path.get(), 0, nullptr);
if(dir) {
const char* basename;
while((basename = g_dir_read_name(dir)) != nullptr) {
/* we only want filenames with .thumbnailer extension */
if(G_LIKELY(g_str_has_suffix(basename, ".thumbnailer"))) {
hash.insert(std::make_pair(basename, data_dir));
}
}
g_dir_close(dir);
}
}
void Thumbnailer::loadAll() {
const gchar* const* data_dirs = g_get_system_data_dirs();
const gchar* const* data_dir;
/* use a temporary hash table to collect thumbnailer basenames
* key: basename of thumbnailer entry file
* value: data dir the thumbnailer entry file is in */
std::unordered_map<std::string, const char*> hash;
/* load user-specific thumbnailers */
find_thumbnailers_in_data_dir(hash, g_get_user_data_dir());
/* load system-wide thumbnailers */
for(data_dir = data_dirs; *data_dir; ++data_dir) {
find_thumbnailers_in_data_dir(hash, *data_dir);
}
/* load all found thumbnailers */
if(!hash.empty()) {
std::lock_guard<std::mutex> lock{mutex_};
GKeyFile* kf = g_key_file_new();
for(auto& item: hash) {
auto& base_name = item.first;
auto& dir_path = item.second;
CStrPtr file_path{g_build_filename(dir_path, "thumbnailers", base_name.c_str(), nullptr)};
if(g_key_file_load_from_file(kf, file_path.get(), G_KEY_FILE_NONE, nullptr)) {
auto thumbnailer = std::make_shared<Thumbnailer>(base_name.c_str(), kf);
if(thumbnailer->exec_) {
char** mime_types = g_key_file_get_string_list(kf, "Thumbnailer Entry", "MimeType", nullptr, nullptr);
if(mime_types) {
for(char** name = mime_types; *name; ++name) {
auto mime_type = MimeType::fromName(*name);
if(mime_type) {
std::const_pointer_cast<MimeType>(mime_type)->addThumbnailer(thumbnailer);
}
}
g_strfreev(mime_types);
}
}
allThumbnailers_.push_back(std::move(thumbnailer));
}
}
g_key_file_free(kf);
}
}
} // namespace Fm

@ -1,37 +0,0 @@
#ifndef FM2_THUMBNAILER_H
#define FM2_THUMBNAILER_H
#include "../libfmqtglobals.h"
#include "cstrptr.h"
#include <unordered_map>
#include <vector>
#include <memory>
#include <mutex>
namespace Fm {
class MimeType;
class LIBFM_QT_API Thumbnailer {
public:
explicit Thumbnailer(const char *id, GKeyFile *kf);
CStrPtr commandForUri(const char* uri, const char* output_file, guint size) const;
bool run(const char* uri, const char* output_file, int size) const;
static void loadAll();
private:
CStrPtr id_;
CStrPtr try_exec_; /* FIXME: is this useful? */
CStrPtr exec_;
//std::vector<std::shared_ptr<const MimeType>> mimeTypes_;
static std::mutex mutex_;
static std::vector<std::shared_ptr<Thumbnailer>> allThumbnailers_;
};
} // namespace Fm
#endif // FM2_THUMBNAILER_H

@ -1,266 +0,0 @@
#include "thumbnailjob.h"
#include <string>
#include <memory>
#include <algorithm>
#include <libexif/exif-loader.h>
#include <QImageReader>
#include <QDir>
#include "thumbnailer.h"
namespace Fm {
QThreadPool* ThumbnailJob::threadPool_ = nullptr;
bool ThumbnailJob::localFilesOnly_ = true;
int ThumbnailJob::maxThumbnailFileSize_ = 0;
ThumbnailJob::ThumbnailJob(FileInfoList files, int size):
files_{std::move(files)},
size_{size},
md5Calc_{g_checksum_new(G_CHECKSUM_MD5)} {
}
ThumbnailJob::~ThumbnailJob() {
g_checksum_free(md5Calc_);
// qDebug("delete ThumbnailJob");
}
void ThumbnailJob::exec() {
for(auto& file: files_) {
if(isCancelled()) {
break;
}
auto image = loadForFile(file);
Q_EMIT thumbnailLoaded(file, size_, image);
results_.emplace_back(std::move(image));
}
}
QImage ThumbnailJob::readImageFromStream(GInputStream* stream, size_t len) {
// FIXME: should we set a limit here? Otherwise if len is too large, we can run out of memory.
std::unique_ptr<unsigned char[]> buffer{new unsigned char[len]}; // allocate enough buffer
unsigned char* pbuffer = buffer.get();
size_t totalReadSize = 0;
while(!isCancelled() && totalReadSize < len) {
size_t bytesToRead = totalReadSize + 4096 > len ? len - totalReadSize : 4096;
gssize readSize = g_input_stream_read(stream, pbuffer, bytesToRead, cancellable_.get(), nullptr);
if(readSize == 0) { // end of file
break;
}
else if(readSize == -1) { // error
return QImage();
}
totalReadSize += readSize;
pbuffer += readSize;
}
QImage image;
image.loadFromData(buffer.get(), totalReadSize);
return image;
}
QImage ThumbnailJob::loadForFile(const std::shared_ptr<const FileInfo> &file) {
if(!file->canThumbnail()) {
return QImage();
}
// thumbnails are stored in $XDG_CACHE_HOME/thumbnails/large|normal|failed
QString thumbnailDir{g_get_user_cache_dir()};
thumbnailDir += "/thumbnails/";
// don't make thumbnails for files inside the thumbnail directory
if(FilePath::fromLocalPath(thumbnailDir.toLocal8Bit().constData()).isParentOf(file->dirPath())) {
return QImage();
}
const char* subdir = size_ > 128 ? "large" : "normal";
thumbnailDir += subdir;
// generate base name of the thumbnail => {md5 of uri}.png
auto origPath = file->path();
auto uri = origPath.uri();
char thumbnailName[32 + 5];
// calculate md5 hash for the uri of the original file
g_checksum_update(md5Calc_, reinterpret_cast<const unsigned char*>(uri.get()), -1);
memcpy(thumbnailName, g_checksum_get_string(md5Calc_), 32);
mempcpy(thumbnailName + 32, ".png", 5);
g_checksum_reset(md5Calc_); // reset the checksum calculator for next use
QString thumbnailFilename = thumbnailDir;
thumbnailFilename += '/';
thumbnailFilename += thumbnailName;
// qDebug() << "thumbnail:" << file->getName().c_str() << thumbnailFilename;
// try to load the thumbnail file if it exists
QImage thumbnail{thumbnailFilename};
if(thumbnail.isNull() || isThumbnailOutdated(file, thumbnail)) {
// the existing thumbnail cannot be loaded, generate a new one
// create the thumbnail dir as needd (FIXME: Qt file I/O is slow)
QDir().mkpath(thumbnailDir);
thumbnail = generateThumbnail(file, origPath, uri.get(), thumbnailFilename);
}
// resize to the size we need
if(thumbnail.width() > size_ || thumbnail.height() > size_) {
thumbnail = thumbnail.scaled(size_, size_, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
return thumbnail;
}
bool ThumbnailJob::isSupportedImageType(const std::shared_ptr<const MimeType>& mimeType) const {
if(mimeType->isImage()) {
auto supportedTypes = QImageReader::supportedMimeTypes();
auto found = std::find(supportedTypes.cbegin(), supportedTypes.cend(), mimeType->name());
if(found != supportedTypes.cend())
return true;
}
return false;
}
bool ThumbnailJob::isThumbnailOutdated(const std::shared_ptr<const FileInfo>& file, const QImage &thumbnail) const {
QString thumb_mtime = thumbnail.text("Thumb::MTime");
return (thumb_mtime.isEmpty() || thumb_mtime.toULongLong() != file->mtime());
}
bool ThumbnailJob::readJpegExif(GInputStream *stream, QImage& thumbnail, int& rotate_degrees) {
/* try to extract thumbnails embedded in jpeg files */
ExifLoader* exif_loader = exif_loader_new();
while(!isCancelled()) {
unsigned char buf[4096];
gssize read_size = g_input_stream_read(stream, buf, 4096, cancellable_.get(), nullptr);
if(read_size <= 0) { // EOF or error
break;
}
if(exif_loader_write(exif_loader, buf, read_size) == 0) {
break; // no more EXIF data
}
}
ExifData* exif_data = exif_loader_get_data(exif_loader);
exif_loader_unref(exif_loader);
if(exif_data) {
/* reference for EXIF orientation tag:
* https://www.impulseadventure.com/photo/exif-orientation.html */
ExifEntry* orient_ent = exif_data_get_entry(exif_data, EXIF_TAG_ORIENTATION);
if(orient_ent) { /* orientation flag found in EXIF */
gushort orient;
ExifByteOrder bo = exif_data_get_byte_order(exif_data);
/* bo == EXIF_BYTE_ORDER_INTEL ; */
orient = exif_get_short(orient_ent->data, bo);
switch(orient) {
case 1: /* no rotation */
rotate_degrees = 0;
break;
case 8:
rotate_degrees = 90;
break;
case 3:
rotate_degrees = 180;
break;
case 6:
rotate_degrees = 270;
break;
}
}
if(exif_data->data) { // if an embedded thumbnail is available, load it
thumbnail.loadFromData(exif_data->data, exif_data->size);
}
exif_data_unref(exif_data);
}
return !thumbnail.isNull();
}
QImage ThumbnailJob::generateThumbnail(const std::shared_ptr<const FileInfo>& file, const FilePath& origPath, const char* uri, const QString& thumbnailFilename) {
QImage result;
auto mime_type = file->mimeType();
if(isSupportedImageType(mime_type)) {
GFileInputStreamPtr ins{g_file_read(origPath.gfile().get(), cancellable_.get(), nullptr), false};
if(!ins)
return QImage();
bool fromExif = false;
int rotate_degrees = 0;
if(strcmp(mime_type->name(), "image/jpeg") == 0) { // if this is a jpeg file
// try to get the thumbnail embedded in EXIF data
if(readJpegExif(G_INPUT_STREAM(ins.get()), result, rotate_degrees)) {
fromExif = true;
}
}
if(!fromExif) { // not able to generate a thumbnail from the EXIF data
// load the original file and do the scaling ourselves
g_seekable_seek(G_SEEKABLE(ins.get()), 0, G_SEEK_SET, cancellable_.get(), nullptr);
result = readImageFromStream(G_INPUT_STREAM(ins.get()), file->size());
}
g_input_stream_close(G_INPUT_STREAM(ins.get()), nullptr, nullptr);
if(!result.isNull()) { // the image is successfully loaded
// scale the image as needed
int target_size = size_ > 128 ? 256 : 128;
// only scale the original image if it's too large
if(result.width() > target_size || result.height() > target_size) {
result = result.scaled(target_size, target_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
if(rotate_degrees != 0) {
// degree values are 0, 90, 180, and 270 counterclockwise.
// In Qt, QMatrix does rotation counterclockwise as well.
// However, because the y axis of widget coordinate system is downward,
// the real effect of the coordinate transformation becomes clockwise rotation.
// So we need to use (360 - degree) here.
// Quote from QMatrix API doc:
// Note that if you apply a QMatrix to a point defined in widget
// coordinates, the direction of the rotation will be clockwise because
// the y-axis points downwards.
result = result.transformed(QMatrix().rotate(360 - rotate_degrees));
}
// save the generated thumbnail to disk (don't save png thumbnails for JPEG EXIF thumbnails since loading them is cheap)
if(!fromExif) {
result.setText("Thumb::MTime", QString::number(file->mtime()));
result.setText("Thumb::URI", uri);
result.save(thumbnailFilename, "PNG");
}
// qDebug() << "save thumbnail:" << thumbnailFilename;
}
}
else { // the image format is not supported, try to find an external thumbnailer
// try all available external thumbnailers for it until sucess
int target_size = size_ > 128 ? 256 : 128;
file->mimeType()->forEachThumbnailer([&](const std::shared_ptr<const Thumbnailer>& thumbnailer) {
if(thumbnailer->run(uri, thumbnailFilename.toLocal8Bit().constData(), target_size)) {
result = QImage(thumbnailFilename);
}
return !result.isNull(); // return true on success, and forEachThumbnailer() will stop.
});
if(!result.isNull()) {
// Some thumbnailers did not write the proper metadata required by the xdg spec to the output (such as evince-thumbnailer)
// Here we waste some time to fix them so next time we don't need to re-generate these thumbnails. :-(
bool changed = false;
if(Q_UNLIKELY(result.text("Thumb::MTime").isEmpty())) {
result.setText("Thumb::MTime", QString::number(file->mtime()));
changed = true;
}
if(Q_UNLIKELY(result.text("Thumb::URI").isEmpty())) {
result.setText("Thumb::URI", uri);
changed = true;
}
if(Q_UNLIKELY(changed)) {
// save the modified PNG file containing metadata to a file.
result.save(thumbnailFilename, "PNG");
}
}
}
return result;
}
QThreadPool* ThumbnailJob::threadPool() {
if(Q_UNLIKELY(threadPool_ == nullptr)) {
threadPool_ = new QThreadPool();
threadPool_->setMaxThreadCount(1);
}
return threadPool_;
}
} // namespace Fm

@ -1,89 +0,0 @@
#ifndef FM2_THUMBNAILJOB_H
#define FM2_THUMBNAILJOB_H
#include "../libfmqtglobals.h"
#include "fileinfo.h"
#include "gioptrs.h"
#include "job.h"
#include <QThreadPool>
namespace Fm {
class LIBFM_QT_API ThumbnailJob: public Job {
Q_OBJECT
public:
explicit ThumbnailJob(FileInfoList files, int size);
~ThumbnailJob();
int size() const {
return size_;
}
static QThreadPool* threadPool();
static void setLocalFilesOnly(bool value) {
localFilesOnly_ = value;
if(fm_config) {
fm_config->thumbnail_local = localFilesOnly_;
}
}
static bool localFilesOnly() {
return localFilesOnly_;
}
static int maxThumbnailFileSize() {
return maxThumbnailFileSize_;
}
static void setMaxThumbnailFileSize(int size) {
maxThumbnailFileSize_ = size;
if(fm_config) {
fm_config->thumbnail_max = maxThumbnailFileSize_;
}
}
const std::vector<QImage>& results() const {
return results_;
}
Q_SIGNALS:
void thumbnailLoaded(const std::shared_ptr<const FileInfo>& file, int size, QImage thumbnail);
protected:
void exec() override;
private:
bool isSupportedImageType(const std::shared_ptr<const MimeType>& mimeType) const;
bool isThumbnailOutdated(const std::shared_ptr<const FileInfo>& file, const QImage& thumbnail) const;
QImage generateThumbnail(const std::shared_ptr<const FileInfo>& file, const FilePath& origPath, const char* uri, const QString& thumbnailFilename);
QImage readImageFromStream(GInputStream* stream, size_t len);
QImage loadForFile(const std::shared_ptr<const FileInfo>& file);
bool readJpegExif(GInputStream* stream, QImage& thumbnail, int& rotate_degrees);
private:
FileInfoList files_;
int size_;
std::vector<QImage> results_;
GCancellablePtr cancellable_;
GChecksum* md5Calc_;
static QThreadPool* threadPool_;
static bool localFilesOnly_;
static int maxThumbnailFileSize_;
};
} // namespace Fm
#endif // FM2_THUMBNAILJOB_H

@ -1,141 +0,0 @@
#include "totalsizejob.h"
namespace Fm {
static const char query_str[] =
G_FILE_ATTRIBUTE_STANDARD_TYPE","
G_FILE_ATTRIBUTE_STANDARD_NAME","
G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL","
G_FILE_ATTRIBUTE_STANDARD_SIZE","
G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE","
G_FILE_ATTRIBUTE_ID_FILESYSTEM;
TotalSizeJob::TotalSizeJob(FilePathList paths, Flags flags):
paths_{std::move(paths)},
flags_{flags},
totalSize_{0},
totalOndiskSize_{0},
fileCount_{0},
dest_fs_id{nullptr} {
}
void TotalSizeJob::exec(FilePath path, GFileInfoPtr inf) {
GFileType type;
const char* fs_id;
bool descend;
_retry_query_info:
if(!inf) {
GErrorPtr err;
inf = GFileInfoPtr {
g_file_query_info(path.gfile().get(), query_str,
(flags_ & FOLLOW_LINKS) ? G_FILE_QUERY_INFO_NONE : G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err),
false
};
if(!inf) {
ErrorAction act = emitError( err, ErrorSeverity::MILD);
err = nullptr;
if(act == ErrorAction::RETRY) {
goto _retry_query_info;
}
return;
}
}
if(isCancelled()) {
return;
}
type = g_file_info_get_file_type(inf.get());
descend = true;
++fileCount_;
/* SF bug #892: dir file size is not relevant in the summary */
if(type != G_FILE_TYPE_DIRECTORY) {
totalSize_ += g_file_info_get_size(inf.get());
}
totalOndiskSize_ += g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE);
/* prepare for moving across different devices */
if(flags_ & PREPARE_MOVE) {
fs_id = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_ID_FILESYSTEM);
if(fs_id && dest_fs_id && (strcmp(fs_id, dest_fs_id) == 0 || g_str_has_prefix(fs_id, "trash"))) {
// same filesystem or move from trash:///
descend = false;
}
else {
/* files on different device requires an additional 'delete' for the source file. */
++totalSize_; /* this is for the additional delete */
++totalOndiskSize_;
++fileCount_;
descend = true;
}
}
if(type == G_FILE_TYPE_DIRECTORY) {
/* check if we need to decends into the dir. */
/* trash:/// doesn't support deleting files recursively (but we want to descend into trash root "trash:///" */
if(flags_ & PREPARE_DELETE && path.hasUriScheme("trash") && path.baseName()[0] != '/') {
descend = false;
}
else {
/* only descends into files on the same filesystem */
if(flags_ & SAME_FS) {
fs_id = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_ID_FILESYSTEM);
descend = (g_strcmp0(fs_id, dest_fs_id) == 0);
}
}
inf = nullptr;
if(descend) {
_retry_enum_children:
GErrorPtr err;
auto enu = GFileEnumeratorPtr {
g_file_enumerate_children(path.gfile().get(), query_str,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(), &err),
false
};
if(enu) {
while(!isCancelled()) {
inf = GFileInfoPtr{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false};
if(inf) {
FilePath child = path.child(g_file_info_get_name(inf.get()));
exec(std::move(child), std::move(inf));
}
else {
if(err) { /* error! */
/* ErrorAction::RETRY is not supported */
emitError( err, ErrorSeverity::MILD);
err = nullptr;
}
else {
/* EOF is reached, do nothing. */
break;
}
}
}
g_file_enumerator_close(enu.get(), nullptr, nullptr);
}
else {
ErrorAction act = emitError( err, ErrorSeverity::MILD);
err = nullptr;
if(act == ErrorAction::RETRY) {
goto _retry_enum_children;
}
}
}
}
}
void TotalSizeJob::exec() {
for(auto& path : paths_) {
exec(path, GFileInfoPtr{});
}
}
} // namespace Fm

@ -1,56 +0,0 @@
#ifndef FM2_TOTALSIZEJOB_H
#define FM2_TOTALSIZEJOB_H
#include "../libfmqtglobals.h"
#include "fileoperationjob.h"
#include "filepath.h"
#include <cstdint>
#include "gioptrs.h"
namespace Fm {
class LIBFM_QT_API TotalSizeJob : public Fm::FileOperationJob {
Q_OBJECT
public:
enum Flags {
DEFAULT = 0,
FOLLOW_LINKS = 1 << 0,
SAME_FS = 1 << 1,
PREPARE_MOVE = 1 << 2,
PREPARE_DELETE = 1 << 3
};
explicit TotalSizeJob(FilePathList paths = FilePathList{}, Flags flags = DEFAULT);
std::uint64_t totalSize() const {
return totalSize_;
}
std::uint64_t totalOnDiskSize() const {
return totalOndiskSize_;
}
unsigned int fileCount() const {
return fileCount_;
}
protected:
void exec() override;
private:
void exec(FilePath path, GFileInfoPtr inf);
private:
FilePathList paths_;
int flags_;
std::uint64_t totalSize_;
std::uint64_t totalOndiskSize_;
unsigned int fileCount_;
const char* dest_fs_id;
};
} // namespace Fm
#endif // FM2_TOTALSIZEJOB_H

@ -1,71 +0,0 @@
#include "trashjob.h"
namespace Fm {
TrashJob::TrashJob(FilePathList paths): paths_{std::move(paths)} {
// calculate progress using finished file counts rather than their sizes
setCalcProgressUsingSize(false);
}
void TrashJob::exec() {
setTotalAmount(paths_.size(), paths_.size());
Q_EMIT preparedToRun();
/* FIXME: we shouldn't trash a file already in trash:/// */
for(auto& path : paths_) {
if(isCancelled()) {
break;
}
setCurrentFile(path);
// TODO: get parent dir of the current path.
// if there is a Fm::Folder object created for it, block the update for the folder temporarily.
for(;;) { // retry the i/o operation on errors
auto gf = path.gfile();
bool ret = false;
// FIXME: do not depend on fm_config
if(fm_config->no_usb_trash) {
GMountPtr mnt{g_file_find_enclosing_mount(gf.get(), nullptr, nullptr), false};
if(mnt) {
ret = g_mount_can_unmount(mnt.get()); /* TRUE if it's removable media */
if(ret) {
unsupportedFiles_.push_back(path);
break; // don't trash the file
}
}
}
// move the file to trash
GErrorPtr err;
ret = g_file_trash(gf.get(), cancellable().get(), &err);
if(ret) { // trash operation succeeded
break;
}
else { // failed
// if trashing is not supported by the file system
if(err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_NOT_SUPPORTED) {
unsupportedFiles_.push_back(path);
}
else {
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
if(act == ErrorAction::RETRY) {
err.reset();
}
else if(act == ErrorAction::ABORT) {
cancel();
return;
}
else {
break;
}
}
}
}
addFinishedAmount(1, 1);
}
}
} // namespace Fm

@ -1,30 +0,0 @@
#ifndef FM2_TRASHJOB_H
#define FM2_TRASHJOB_H
#include "../libfmqtglobals.h"
#include "fileoperationjob.h"
#include "filepath.h"
namespace Fm {
class LIBFM_QT_API TrashJob : public Fm::FileOperationJob {
Q_OBJECT
public:
explicit TrashJob(FilePathList paths);
FilePathList unsupportedFiles() const {
return unsupportedFiles_;
}
protected:
void exec() override;
private:
FilePathList paths_;
FilePathList unsupportedFiles_;
};
} // namespace Fm
#endif // FM2_TRASHJOB_H

@ -1,76 +0,0 @@
#include "untrashjob.h"
#include "filetransferjob.h"
namespace Fm {
UntrashJob::UntrashJob(FilePathList srcPaths):
srcPaths_{std::move(srcPaths)} {
}
void UntrashJob::exec() {
// preparing for the job
FilePathList validSrcPaths;
FilePathList origPaths;
for(auto& srcPath: srcPaths_) {
if(isCancelled()) {
break;
}
GErrorPtr err;
GFileInfoPtr srcInfo{
g_file_query_info(srcPath.gfile().get(),
"trash::orig-path",
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable().get(),
&err),
false
};
if(srcInfo) {
const char* orig_path_str = g_file_info_get_attribute_byte_string(srcInfo.get(), "trash::orig-path");
if(orig_path_str) {
validSrcPaths.emplace_back(srcPath);
origPaths.emplace_back(FilePath::fromPathStr(orig_path_str));
}
else {
ErrorAction act;
g_set_error(&err, G_IO_ERROR, G_IO_ERROR_FAILED,
tr("Cannot untrash file '%s': original path not known").toUtf8().constData(),
g_file_info_get_display_name(srcInfo.get()));
// FIXME: do we need to retry here?
emitError(err, ErrorSeverity::MODERATE);
}
}
else {
// FIXME: do we need to retry here?
emitError(err);
}
}
// collected original paths of the trashed files
// use the file transfer job to handle the actual file move
FileTransferJob fileTransferJob{std::move(validSrcPaths), std::move(origPaths), FileTransferJob::Mode::MOVE};
// FIXME:
// I'm not sure why specifying Qt::DirectConnection is needed here since the caller & receiver are in the same thread. :-(
// However without this, the signals/slots here will cause deadlocks.
connect(&fileTransferJob, &FileTransferJob::preparedToRun, this, &UntrashJob::preparedToRun, Qt::DirectConnection);
connect(&fileTransferJob, &FileTransferJob::error, this, &UntrashJob::error, Qt::DirectConnection);
connect(&fileTransferJob, &FileTransferJob::fileExists, this, &UntrashJob::fileExists, Qt::DirectConnection);
// cancel the file transfer subjob if the parent job is cancelled
connect(this, &UntrashJob::cancelled, &fileTransferJob,
[&fileTransferJob]() {
if(!fileTransferJob.isCancelled()) {
fileTransferJob.cancel();
}
}, Qt::DirectConnection);
// cancel the parent job if the file transfer subjob is cancelled
connect(&fileTransferJob, &FileTransferJob::cancelled, this,
[this]() {
if(!isCancelled()) {
cancel();
}
}, Qt::DirectConnection);
fileTransferJob.run();
}
} // namespace Fm

@ -1,22 +0,0 @@
#ifndef FM2_UNTRASHJOB_H
#define FM2_UNTRASHJOB_H
#include "../libfmqtglobals.h"
#include "fileoperationjob.h"
namespace Fm {
class LIBFM_QT_API UntrashJob : public FileOperationJob {
public:
explicit UntrashJob(FilePathList srcPaths);
protected:
void exec() override;
private:
FilePathList srcPaths_;
};
} // namespace Fm
#endif // FM2_UNTRASHJOB_H

@ -1,47 +0,0 @@
#include "userinfocache.h"
#include <pwd.h>
#include <grp.h>
namespace Fm {
UserInfoCache* UserInfoCache::globalInstance_ = nullptr;
std::mutex UserInfoCache::mutex_;
UserInfoCache::UserInfoCache() : QObject() {
}
const std::shared_ptr<const UserInfo>& UserInfoCache::userFromId(uid_t uid) {
std::lock_guard<std::mutex> lock{mutex_};
auto it = users_.find(uid);
if(it != users_.end())
return it->second;
std::shared_ptr<const UserInfo> user;
auto pw = getpwuid(uid);
if(pw) {
user = std::make_shared<UserInfo>(uid, pw->pw_name, pw->pw_gecos);
}
return (users_[uid] = user);
}
const std::shared_ptr<const GroupInfo>& UserInfoCache::groupFromId(gid_t gid) {
std::lock_guard<std::mutex> lock{mutex_};
auto it = groups_.find(gid);
if(it != groups_.end())
return it->second;
std::shared_ptr<const GroupInfo> group;
auto gr = getgrgid(gid);
if(gr) {
group = std::make_shared<GroupInfo>(gid, gr->gr_name);
}
return (groups_[gid] = group);
}
// static
UserInfoCache* UserInfoCache::globalInstance() {
std::lock_guard<std::mutex> lock{mutex_};
if(!globalInstance_)
globalInstance_ = new UserInfoCache();
return globalInstance_;
}
} // namespace Fm

@ -1,82 +0,0 @@
#ifndef FM2_USERINFOCACHE_H
#define FM2_USERINFOCACHE_H
#include "../libfmqtglobals.h"
#include <QObject>
#include <string>
#include <unordered_map>
#include <sys/types.h>
#include <memory>
#include <mutex>
namespace Fm {
class LIBFM_QT_API UserInfo {
public:
explicit UserInfo(uid_t uid, const char* name, const char* realName):
uid_{uid}, name_{name}, realName_{realName} {
}
uid_t uid() const {
return uid_;
}
const QString& name() const {
return name_;
}
const QString& realName() const {
return realName_;
}
private:
uid_t uid_;
QString name_;
QString realName_;
};
class LIBFM_QT_API GroupInfo {
public:
explicit GroupInfo(gid_t gid, const char* name): gid_{gid}, name_{name} {
}
gid_t gid() const {
return gid_;
}
const QString& name() const {
return name_;
}
private:
gid_t gid_;
QString name_;
};
// FIXME: handle file changes
class LIBFM_QT_API UserInfoCache : public QObject {
Q_OBJECT
public:
explicit UserInfoCache();
const std::shared_ptr<const UserInfo>& userFromId(uid_t uid);
const std::shared_ptr<const GroupInfo>& groupFromId(gid_t gid);
static UserInfoCache* globalInstance();
Q_SIGNALS:
void changed();
private:
std::unordered_map<uid_t, std::shared_ptr<const UserInfo>> users_;
std::unordered_map<gid_t, std::shared_ptr<const GroupInfo>> groups_;
static UserInfoCache* globalInstance_;
static std::mutex mutex_;
};
} // namespace Fm
#endif // FM2_USERINFOCACHE_H

@ -1,111 +0,0 @@
#include "volumemanager.h"
namespace Fm {
std::mutex VolumeManager::mutex_;
std::weak_ptr<VolumeManager> VolumeManager::globalInstance_;
VolumeManager::VolumeManager():
QObject(),
monitor_{g_volume_monitor_get(), false} {
// connect gobject signal handlers
g_signal_connect(monitor_.get(), "volume-added", G_CALLBACK(_onGVolumeAdded), this);
g_signal_connect(monitor_.get(), "volume-removed", G_CALLBACK(_onGVolumeRemoved), this);
g_signal_connect(monitor_.get(), "volume-changed", G_CALLBACK(_onGVolumeChanged), this);
g_signal_connect(monitor_.get(), "mount-added", G_CALLBACK(_onGMountAdded), this);
g_signal_connect(monitor_.get(), "mount-removed", G_CALLBACK(_onGMountRemoved), this);
g_signal_connect(monitor_.get(), "mount-changed", G_CALLBACK(_onGMountChanged), this);
// g_get_volume_monitor() is a slow blocking call, so call it in a low priority thread
auto job = new GetGVolumeMonitorJob();
job->setAutoDelete(true);
connect(job, &GetGVolumeMonitorJob::finished, this, &VolumeManager::onGetGVolumeMonitorFinished, Qt::BlockingQueuedConnection);
job->runAsync(QThread::LowPriority);
}
VolumeManager::~VolumeManager() {
if(monitor_) {
g_signal_handlers_disconnect_by_data(monitor_.get(), this);
}
}
std::shared_ptr<VolumeManager> VolumeManager::globalInstance() {
std::lock_guard<std::mutex> lock{mutex_};
auto mon = globalInstance_.lock();
if(mon == nullptr) {
mon = std::make_shared<VolumeManager>();
globalInstance_ = mon;
}
return mon;
}
void VolumeManager::onGetGVolumeMonitorFinished() {
auto job = static_cast<GetGVolumeMonitorJob*>(sender());
monitor_ = std::move(job->monitor_);
GList* vols = g_volume_monitor_get_volumes(monitor_.get());
for(GList* l = vols; l != nullptr; l = l->next) {
volumes_.push_back(Volume{G_VOLUME(l->data), false});
Q_EMIT volumeAdded(volumes_.back());
}
g_list_free(vols);
GList* mnts = g_volume_monitor_get_mounts(monitor_.get());
for(GList* l = mnts; l != nullptr; l = l->next) {
mounts_.push_back(Mount{G_MOUNT(l->data), false});
Q_EMIT mountAdded(mounts_.back());
}
g_list_free(mnts);
}
void VolumeManager::onGVolumeAdded(GVolume* vol) {
if(std::find(volumes_.cbegin(), volumes_.cend(), vol) != volumes_.cend())
return;
volumes_.push_back(Volume{vol, true});
Q_EMIT volumeAdded(volumes_.back());
}
void VolumeManager::onGVolumeRemoved(GVolume* vol) {
auto it = std::find(volumes_.begin(), volumes_.end(), vol);
if(it == volumes_.end())
return;
Q_EMIT volumeRemoved(*it);
volumes_.erase(it);
}
void VolumeManager::onGVolumeChanged(GVolume* vol) {
auto it = std::find(volumes_.begin(), volumes_.end(), vol);
if(it == volumes_.end())
return;
Q_EMIT volumeChanged(*it);
}
void VolumeManager::onGMountAdded(GMount* mnt) {
if(std::find(mounts_.cbegin(), mounts_.cend(), mnt) != mounts_.cend())
return;
mounts_.push_back(Mount{mnt, true});
Q_EMIT mountAdded(mounts_.back());
}
void VolumeManager::onGMountRemoved(GMount* mnt) {
auto it = std::find(mounts_.begin(), mounts_.end(), mnt);
if(it == mounts_.end())
return;
Q_EMIT mountRemoved(*it);
mounts_.erase(it);
}
void VolumeManager::onGMountChanged(GMount* mnt) {
auto it = std::find(mounts_.begin(), mounts_.end(), mnt);
if(it == mounts_.end())
return;
Q_EMIT mountChanged(*it);
}
void VolumeManager::GetGVolumeMonitorJob::exec() {
monitor_ = GVolumeMonitorPtr{g_volume_monitor_get(), false};
}
} // namespace Fm

@ -1,237 +0,0 @@
#ifndef FM2_VOLUMEMANAGER_H
#define FM2_VOLUMEMANAGER_H
#include "../libfmqtglobals.h"
#include <QObject>
#include <gio/gio.h>
#include "gioptrs.h"
#include "filepath.h"
#include "iconinfo.h"
#include "job.h"
#include <vector>
#include <mutex>
namespace Fm {
class LIBFM_QT_API Volume: public GVolumePtr {
public:
explicit Volume(GVolume* gvol, bool addRef): GVolumePtr{gvol, addRef} {
}
explicit Volume(GVolumePtr gvol): GVolumePtr{std::move(gvol)} {
}
CStrPtr name() const {
return CStrPtr{g_volume_get_name(get())};
}
CStrPtr uuid() const {
return CStrPtr{g_volume_get_uuid(get())};
}
std::shared_ptr<const IconInfo> icon() const {
return IconInfo::fromGIcon(GIconPtr{g_volume_get_icon(get()), false});
}
// GDrive * g_volume_get_drive(get());
GMountPtr mount() const {
return GMountPtr{g_volume_get_mount(get()), false};
}
bool canMount() const {
return g_volume_can_mount(get());
}
bool shouldAutoMount() const {
return g_volume_should_automount(get());
}
FilePath activationRoot() const {
return FilePath{g_volume_get_activation_root(get()), false};
}
/*
void g_volume_mount(get());
gboolean g_volume_mount_finish(get());
*/
bool canEject() const {
return g_volume_can_eject(get());
}
/*
void g_volume_eject(get());
gboolean g_volume_eject_finish(get());
void g_volume_eject_with_operation(get());
gboolean g_volume_eject_with_operation_finish(get());
char ** g_volume_enumerate_identifiers(get());
char * g_volume_get_identifier(get());
const gchar * g_volume_get_sort_key(get());
*/
CStrPtr device() const {
return CStrPtr{g_volume_get_identifier(get(), G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE)};
}
CStrPtr label() const {
return CStrPtr{g_volume_get_identifier(get(), G_VOLUME_IDENTIFIER_KIND_LABEL)};
}
};
class LIBFM_QT_API Mount: public GMountPtr {
public:
explicit Mount(GMount* mnt, bool addRef): GMountPtr{mnt, addRef} {
}
explicit Mount(GMountPtr gmnt): GMountPtr{std::move(gmnt)} {
}
CStrPtr name() const {
return CStrPtr{g_mount_get_name(get())};
}
CStrPtr uuid() const {
return CStrPtr{g_mount_get_uuid(get())};
}
std::shared_ptr<const IconInfo> icon() const {
return IconInfo::fromGIcon(GIconPtr{g_mount_get_icon(get()), false});
}
// GIcon * g_mount_get_symbolic_icon(get());
// GDrive * g_mount_get_drive(get());
FilePath root() const {
return FilePath{g_mount_get_root(get()), false};
}
GVolumePtr volume() const {
return GVolumePtr{g_mount_get_volume(get()), false};
}
FilePath defaultLocation() const {
return FilePath{g_mount_get_default_location(get()), false};
}
bool canUnmount() const {
return g_mount_can_unmount(get());
}
/*
void g_mount_unmount(get());
gboolean g_mount_unmount_finish(get());
void g_mount_unmount_with_operation(get());
gboolean g_mount_unmount_with_operation_finish(get());
void g_mount_remount(get());
gboolean g_mount_remount_finish(get());
*/
bool canEject() const {
return g_mount_can_eject(get());
}
/*
void g_mount_eject(get());
gboolean g_mount_eject_finish(get());
void g_mount_eject_with_operation(get());
gboolean g_mount_eject_with_operation_finish(get());
*/
// void g_mount_guess_content_type(get());
// gchar ** g_mount_guess_content_type_finish(get());
// gchar ** g_mount_guess_content_type_sync(get());
bool isShadowed() const {
return g_mount_is_shadowed(get());
}
// void g_mount_shadow(get());
// void g_mount_unshadow(get());
// const gchar * g_mount_get_sort_key(get());
};
class LIBFM_QT_API VolumeManager : public QObject {
Q_OBJECT
public:
explicit VolumeManager();
~VolumeManager();
const std::vector<Volume>& volumes() const {
return volumes_;
}
const std::vector<Mount>& mounts() const {
return mounts_;
}
static std::shared_ptr<VolumeManager> globalInstance();
Q_SIGNALS:
void volumeAdded(const Volume& vol);
void volumeRemoved(const Volume& vol);
void volumeChanged(const Volume& vol);
void mountAdded(const Mount& mnt);
void mountRemoved(const Mount& mnt);
void mountChanged(const Mount& mnt);
public Q_SLOTS:
void onGetGVolumeMonitorFinished();
private:
class GetGVolumeMonitorJob: public Job {
public:
GetGVolumeMonitorJob() {}
GVolumeMonitorPtr monitor_;
protected:
void exec() override;
};
static void _onGVolumeAdded(GVolumeMonitor* /*mon*/, GVolume* vol, VolumeManager* _this) {
_this->onGVolumeAdded(vol);
}
void onGVolumeAdded(GVolume* vol);
static void _onGVolumeRemoved(GVolumeMonitor* /*mon*/, GVolume* vol, VolumeManager* _this) {
_this->onGVolumeRemoved(vol);
}
void onGVolumeRemoved(GVolume* vol);
static void _onGVolumeChanged(GVolumeMonitor* /*mon*/, GVolume* vol, VolumeManager* _this) {
_this->onGVolumeChanged(vol);
}
void onGVolumeChanged(GVolume* vol);
static void _onGMountAdded(GVolumeMonitor* /*mon*/, GMount* mnt, VolumeManager* _this) {
_this->onGMountAdded(mnt);
}
void onGMountAdded(GMount* mnt);
static void _onGMountRemoved(GVolumeMonitor* /*mon*/, GMount* mnt, VolumeManager* _this) {
_this->onGMountRemoved(mnt);
}
void onGMountRemoved(GMount* mnt);
static void _onGMountChanged(GVolumeMonitor* /*mon*/, GMount* mnt, VolumeManager* _this) {
_this->onGMountChanged(mnt);
}
void onGMountChanged(GMount* mnt);
private:
GVolumeMonitorPtr monitor_;
std::vector<Volume> volumes_;
std::vector<Mount> mounts_;
static std::mutex mutex_;
static std::weak_ptr<VolumeManager> globalInstance_;
};
} // namespace Fm
#endif // FM2_VOLUMEMANAGER_H

@ -1,152 +0,0 @@
/*
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "createnewmenu.h"
#include "folderview.h"
#include "utilities.h"
#include "core/iconinfo.h"
#include "core/templates.h"
#include <algorithm>
namespace Fm {
class TemplateAction: public QAction {
public:
TemplateAction(std::shared_ptr<const TemplateItem> item, QObject* parent):
QAction(parent) {
setTemplateItem(std::move(item));
}
const std::shared_ptr<const TemplateItem> templateItem() const {
return templateItem_;
}
void setTemplateItem(std::shared_ptr<const TemplateItem> item) {
templateItem_ = std::move(item);
auto mimeType = templateItem_->mimeType();
setText(QString("%1 (%2)").arg(templateItem_->displayName()).arg(mimeType->desc()));
setIcon(templateItem_->icon()->qicon());
}
private:
std::shared_ptr<const TemplateItem> templateItem_;
};
CreateNewMenu::CreateNewMenu(QWidget* dialogParent, Fm::FilePath dirPath, QWidget* parent):
QMenu(parent),
dialogParent_(dialogParent),
dirPath_(std::move(dirPath)),
templateSeparator_{nullptr},
templates_{Templates::globalInstance()} {
QAction* action = new QAction(QIcon::fromTheme("folder-new"), tr("Folder"), this);
connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNewFolder);
addAction(action);
action = new QAction(QIcon::fromTheme("document-new"), tr("Blank File"), this);
connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNewFile);
addAction(action);
// add more items to "Create New" menu from templates
connect(templates_.get(), &Templates::itemAdded, this, &CreateNewMenu::addTemplateItem);
connect(templates_.get(), &Templates::itemChanged, this, &CreateNewMenu::updateTemplateItem);
connect(templates_.get(), &Templates::itemRemoved, this, &CreateNewMenu::removeTemplateItem);
templates_->forEachItem([this](const std::shared_ptr<const TemplateItem>& item) {
addTemplateItem(item);
});
}
CreateNewMenu::~CreateNewMenu() {
}
void CreateNewMenu::onCreateNewFile() {
if(dirPath_) {
createFileOrFolder(CreateNewTextFile, dirPath_);
}
}
void CreateNewMenu::onCreateNewFolder() {
if(dirPath_) {
createFileOrFolder(CreateNewFolder, dirPath_);
}
}
void CreateNewMenu::onCreateNew() {
TemplateAction* action = static_cast<TemplateAction*>(sender());
if(dirPath_) {
createFileOrFolder(CreateWithTemplate, dirPath_, action->templateItem().get(), dialogParent_);
}
}
void CreateNewMenu::addTemplateItem(const std::shared_ptr<const TemplateItem> &item) {
if(!templateSeparator_) {
templateSeparator_= addSeparator();
}
auto mimeType = item->mimeType();
/* we support directories differently */
if(mimeType->isDir()) {
return;
}
QAction* action = new TemplateAction{item, this};
connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNew);
addAction(action);
}
void CreateNewMenu::updateTemplateItem(const std::shared_ptr<const TemplateItem> &oldItem, const std::shared_ptr<const TemplateItem> &newItem) {
auto allActions = actions();
auto separatorPos = allActions.indexOf(templateSeparator_);
// all items after the separator are templates
for(auto i = separatorPos + 1; i < allActions.size(); ++i) {
auto action = static_cast<TemplateAction*>(allActions[i]);
if(action->templateItem() == oldItem) {
// update the menu item
action->setTemplateItem(newItem);
break;
}
}
}
void CreateNewMenu::removeTemplateItem(const std::shared_ptr<const TemplateItem> &item) {
if(!templateSeparator_) {
return;
}
auto allActions = actions();
auto separatorPos = allActions.indexOf(templateSeparator_);
// all items after the separator are templates
for(auto i = separatorPos + 1; i < allActions.size(); ++i) {
auto action = static_cast<TemplateAction*>(allActions[i]);
if(action->templateItem() == item) {
// delete the action from the menu
removeAction(action);
allActions.removeAt(i);
break;
}
}
// no more template items. remove the separator
if(separatorPos == allActions.size() - 1) {
removeAction(templateSeparator_);
templateSeparator_ = nullptr;
}
}
} // namespace Fm

@ -1,65 +0,0 @@
/*
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef FM_CREATENEWMENU_H
#define FM_CREATENEWMENU_H
#include "libfmqtglobals.h"
#include <QMenu>
#include <libfm/fm.h>
#include "core/filepath.h"
namespace Fm {
class FolderView;
class Templates;
class TemplateItem;
class LIBFM_QT_API CreateNewMenu : public QMenu {
Q_OBJECT
public:
explicit CreateNewMenu(QWidget* dialogParent, Fm::FilePath dirPath, QWidget* parent = 0);
virtual ~CreateNewMenu();
protected Q_SLOTS:
void onCreateNewFolder();
void onCreateNewFile();
void onCreateNew();
private Q_SLOTS:
void addTemplateItem(const std::shared_ptr<const TemplateItem>& item);
void updateTemplateItem(const std::shared_ptr<const TemplateItem>& oldItem, const std::shared_ptr<const TemplateItem>& newItem);
void removeTemplateItem(const std::shared_ptr<const TemplateItem>& item);
private:
QWidget* dialogParent_;
Fm::FilePath dirPath_;
QAction* templateSeparator_;
std::shared_ptr<Templates> templates_;
};
}
#endif // FM_CREATENEWMENU_H

@ -1,52 +0,0 @@
/*
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef FM_CUSTOMACTION_P_H
#define FM_CUSTOMACTION_P_H
#include <QAction>
#include "customactions/fileaction.h"
namespace Fm {
class CustomAction : public QAction {
public:
explicit CustomAction(std::shared_ptr<const FileActionItem> item, QObject* parent = nullptr):
QAction{QString::fromStdString(item->get_name()), parent},
item_{item} {
auto& icon_name = item->get_icon();
if(!icon_name.empty()) {
setIcon(QIcon::fromTheme(icon_name.c_str()));
}
}
virtual ~CustomAction() {
}
const std::shared_ptr<const FileActionItem>& item() const {
return item_;
}
private:
std::shared_ptr<const FileActionItem> item_;
};
} // namespace Fm
#endif

@ -1,615 +0,0 @@
#include "fileaction.h"
#include <unordered_map>
#include <QDebug>
using namespace std;
namespace Fm {
static const char* desktop_env = nullptr; // current desktop environment
static bool actions_loaded = false; // all actions are loaded?
static unordered_map<const char*, shared_ptr<FileActionObject>, CStrHash, CStrEqual> all_actions; // cache all loaded actions
FileActionObject::FileActionObject() {
}
FileActionObject::FileActionObject(GKeyFile* kf) {
name = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "Name", nullptr, nullptr)};
tooltip = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "Tooltip", nullptr, nullptr)};
icon = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "Icon", nullptr, nullptr)};
desc = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "Description", nullptr, nullptr)};
GErrorPtr err;
enabled = g_key_file_get_boolean(kf, "Desktop Entry", "Enabled", &err);
if(err) { // key not found, default to true
err.reset();
enabled = true;
}
hidden = g_key_file_get_boolean(kf, "Desktop Entry", "Hidden", nullptr);
suggested_shortcut = CStrPtr{g_key_file_get_string(kf, "Desktop Entry", "SuggestedShortcut", nullptr)};
condition = unique_ptr<FileActionCondition> {new FileActionCondition(kf, "Desktop Entry")};
has_parent = false;
}
FileActionObject::~FileActionObject() {
}
//static
bool FileActionObject::is_plural_exec(const char* exec) {
if(!exec) {
return false;
}
// the first relevent code encountered in Exec parameter
// determines whether the command accepts singular or plural forms
for(int i = 0; exec[i]; ++i) {
char ch = exec[i];
if(ch == '%') {
++i;
ch = exec[i];
switch(ch) {
case 'B':
case 'D':
case 'F':
case 'M':
case 'O':
case 'U':
case 'W':
case 'X':
return true; // plural
case 'b':
case 'd':
case 'f':
case 'm':
case 'o':
case 'u':
case 'w':
case 'x':
return false; // singular
default:
// irrelevent code, skip
break;
}
}
}
return false; // singular form by default
}
std::string FileActionObject::expand_str(const char* templ, const FileInfoList& files, bool for_display, std::shared_ptr<const FileInfo> first_file) {
if(!templ) {
return string{};
}
string result;
if(!first_file) {
first_file = files.front();
}
for(int i = 0; templ[i]; ++i) {
char ch = templ[i];
if(ch == '%') {
++i;
ch = templ[i];
switch(ch) {
case 'b': // (first) basename
if(for_display) {
result += first_file->name();
}
else {
CStrPtr quoted{g_shell_quote(first_file->name().c_str())};
result += quoted.get();
}
break;
case 'B': // space-separated list of basenames
for(auto& fi : files) {
if(for_display) {
result += fi->name();
}
else {
CStrPtr quoted{g_shell_quote(fi->name().c_str())};
result += quoted.get();
}
result += ' ';
}
if(result[result.length() - 1] == ' ') { // remove trailing space
result.erase(result.length() - 1);
}
break;
case 'c': // count of selected items
result += to_string(files.size());
break;
case 'd': { // (first) base directory
// FIXME: should the base dir be a URI?
auto base_dir = first_file->dirPath();
auto str = base_dir.toString();
if(for_display) {
// FIXME: str = Filename.display_name(str);
}
CStrPtr quoted{g_shell_quote(str.get())};
result += quoted.get();
break;
}
case 'D': // space-separated list of base directory of each selected items
for(auto& fi : files) {
auto base_dir = fi->dirPath();
auto str = base_dir.toString();
if(for_display) {
// str = Filename.display_name(str);
}
CStrPtr quoted{g_shell_quote(str.get())};
result += quoted.get();
result += ' ';
}
if(result[result.length() - 1] == ' ') { // remove trailing space
result.erase(result.length() - 1);
}
break;
case 'f': { // (first) file name
auto filename = first_file->path().toString();
if(for_display) {
// filename = Filename.display_name(filename);
}
CStrPtr quoted{g_shell_quote(filename.get())};
result += quoted.get();
break;
}
case 'F': // space-separated list of selected file names
for(auto& fi : files) {
auto filename = fi->path().toString();
if(for_display) {
// filename = Filename.display_name(filename);
}
CStrPtr quoted{g_shell_quote(filename.get())};
result += quoted.get();
result += ' ';
}
if(result[result.length() - 1] == ' ') { // remove trailing space
result.erase(result.length() - 1);
}
break;
case 'h': // hostname of the (first) URI
// FIXME: how to support this correctly?
// FIXME: currently we pass g_get_host_name()
result += g_get_host_name();
break;
case 'm': // mimetype of the (first) selected item
result += first_file->mimeType()->name();
break;
case 'M': // space-separated list of the mimetypes of the selected items
for(auto& fi : files) {
result += fi->mimeType()->name();
result += ' ';
}
break;
case 'n': // username of the (first) URI
// FIXME: how to support this correctly?
result += g_get_user_name();
break;
case 'o': // no-op operator which forces a singular form of execution when specified as first parameter,
case 'O': // no-op operator which forces a plural form of execution when specified as first parameter,
break;
case 'p': // port number of the (first) URI
// FIXME: how to support this correctly?
// result.append("0");
break;
case 's': // scheme of the (first) URI
result += first_file->path().uriScheme().get();
break;
case 'u': // (first) URI
result += first_file->path().uri().get();
break;
case 'U': // space-separated list of selected URIs
for(auto& fi : files) {
result += fi->path().uri().get();
result += ' ';
}
if(result[result.length() - 1] == ' ') { // remove trailing space
result.erase(result.length() - 1);
}
break;
case 'w': { // (first) basename without the extension
auto basename = first_file->name();
int pos = basename.rfind('.');
// FIXME: handle non-UTF8 filenames
if(pos != -1) {
basename.erase(pos, string::npos);
}
CStrPtr quoted{g_shell_quote(basename.c_str())};
result += quoted.get();
break;
}
case 'W': // space-separated list of basenames without their extension
for(auto& fi : files) {
auto basename = fi->name();
int pos = basename.rfind('.');
// FIXME: for_display ? Shell.quote(str) : str);
if(pos != -1) {
basename.erase(pos, string::npos);
}
CStrPtr quoted{g_shell_quote(basename.c_str())};
result += quoted.get();
result += ' ';
}
if(result[result.length() - 1] == ' ') { // remove trailing space
result.erase(result.length() - 1);
}
break;
case 'x': { // (first) extension
auto basename = first_file->name();
int pos = basename.rfind('.');
const char* ext = "";
if(pos >= 0) {
ext = basename.c_str() + pos + 1;
}
CStrPtr quoted{g_shell_quote(ext)};
result += quoted.get();
break;
}
case 'X': // space-separated list of extensions
for(auto& fi : files) {
auto basename = fi->name();
int pos = basename.rfind('.');
const char* ext = "";
if(pos >= 0) {
ext = basename.c_str() + pos + 1;
}
CStrPtr quoted{g_shell_quote(ext)};
result += quoted.get();
result += ' ';
}
if(result[result.length() - 1] == ' ') { // remove trailing space
result.erase(result.length() - 1);
}
break;
case '%': // the % character
result += '%';
break;
case '\0':
break;
}
}
else {
result += ch;
}
}
return result;
}
FileAction::FileAction(GKeyFile* kf): FileActionObject{kf}, target{FILE_ACTION_TARGET_CONTEXT} {
type = FileActionType::ACTION;
GErrorPtr err;
if(g_key_file_get_boolean(kf, "Desktop Entry", "TargetContext", &err)) { // default to true
target |= FILE_ACTION_TARGET_CONTEXT;
}
else if(!err) { // error means the key is abscent
target &= ~FILE_ACTION_TARGET_CONTEXT;
}
if(g_key_file_get_boolean(kf, "Desktop Entry", "TargetLocation", nullptr)) {
target |= FILE_ACTION_TARGET_LOCATION;
}
if(g_key_file_get_boolean(kf, "Desktop Entry", "TargetToolbar", nullptr)) {
target |= FILE_ACTION_TARGET_TOOLBAR;
}
toolbar_label = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "ToolbarLabel", nullptr, nullptr)};
auto profile_names = CStrArrayPtr{g_key_file_get_string_list(kf, "Desktop Entry", "Profiles", nullptr, nullptr)};
if(profile_names != nullptr) {
for(auto profile_name = profile_names.get(); *profile_name; ++profile_name) {
// stdout.printf("%s", profile);
profiles.push_back(make_shared<FileActionProfile>(kf, *profile_name));
}
}
}
std::shared_ptr<FileActionProfile> FileAction::match(const FileInfoList& files) const {
//qDebug() << "FileAction.match: " << id.get();
if(hidden || !enabled) {
return nullptr;
}
if(!condition->match(files)) {
return nullptr;
}
for(const auto& profile : profiles) {
if(profile->match(files)) {
//qDebug() << " profile matched!\n\n";
return profile;
}
}
// stdout.printf("\n");
return nullptr;
}
FileActionMenu::FileActionMenu(GKeyFile* kf): FileActionObject{kf} {
type = FileActionType::MENU;
items_list = CStrArrayPtr{g_key_file_get_string_list(kf, "Desktop Entry", "ItemsList", nullptr, nullptr)};
}
bool FileActionMenu::match(const FileInfoList& files) const {
// stdout.printf("FileActionMenu.match: %s\n", id);
if(hidden || !enabled) {
return false;
}
if(!condition->match(files)) {
return false;
}
// stdout.printf("menu matched!: %s\n\n", id);
return true;
}
void FileActionMenu::cache_children(const FileInfoList& files, const char** items_list) {
for(; *items_list; ++items_list) {
const char* item_id_prefix = *items_list;
size_t len = strlen(item_id_prefix);
if(item_id_prefix[0] == '[' && item_id_prefix[len - 1] == ']') {
// runtime dynamic item list
char* output;
int exit_status;
string prefix{item_id_prefix + 1, len - 2}; // skip [ and ]
auto command = expand_str(prefix.c_str(), files);
if(g_spawn_command_line_sync(command.c_str(), &output, nullptr, &exit_status, nullptr) && exit_status == 0) {
CStrArrayPtr item_ids{g_strsplit(output, ";", -1)};
g_free(output);
cache_children(files, (const char**)item_ids.get());
}
}
else if(strcmp(item_id_prefix, "SEPARATOR") == 0) {
// separator item
cached_children.push_back(nullptr);
}
else {
CStrPtr item_id{g_strconcat(item_id_prefix, ".desktop", nullptr)};
auto it = all_actions.find(item_id.get());
if(it != all_actions.end()) {
auto child_action = it->second;
child_action->has_parent = true;
cached_children.push_back(child_action);
// stdout.printf("add child: %s to menu: %s\n", item_id, id);
}
}
}
}
std::shared_ptr<FileActionItem> FileActionItem::fromActionObject(std::shared_ptr<FileActionObject> action_obj, const FileInfoList& files) {
std::shared_ptr<FileActionItem> item;
if(action_obj->type == FileActionType::MENU) {
auto menu = static_pointer_cast<FileActionMenu>(action_obj);
if(menu->match(files)) {
item = make_shared<FileActionItem>(menu, files);
// eliminate empty menus
if(item->children.empty()) {
item = nullptr;
}
}
}
else {
// handle profiles here
auto action = static_pointer_cast<FileAction>(action_obj);
auto profile = action->match(files);
if(profile) {
item = make_shared<FileActionItem>(action, profile, files);
}
}
return item;
}
FileActionItem::FileActionItem(std::shared_ptr<FileAction> _action, std::shared_ptr<FileActionProfile> _profile, const FileInfoList& files):
FileActionItem{static_pointer_cast<FileActionObject>(_action), files} {
profile = _profile;
}
FileActionItem::FileActionItem(std::shared_ptr<FileActionMenu> menu, const FileInfoList& files):
FileActionItem{static_pointer_cast<FileActionObject>(menu), files} {
for(auto& action_obj : menu->cached_children) {
if(action_obj == nullptr) { // separator
children.push_back(nullptr);
}
else { // action item or menu
auto subitem = fromActionObject(action_obj, files);
if(subitem != nullptr) {
children.push_back(subitem);
}
}
}
}
FileActionItem::FileActionItem(std::shared_ptr<FileActionObject> _action, const FileInfoList& files) {
action = std::move(_action);
name = FileActionObject::expand_str(action->name.get(), files, true);
desc = FileActionObject::expand_str(action->desc.get(), files, true);
icon = FileActionObject::expand_str(action->icon.get(), files, false);
}
bool FileActionItem::launch(GAppLaunchContext* ctx, const FileInfoList& files, CStrPtr& output) const {
if(action->type == FileActionType::ACTION) {
if(profile != nullptr) {
profile->launch(ctx, files, output);
}
return true;
}
return false;
}
static void load_actions_from_dir(const char* dirname, const char* id_prefix) {
//qDebug() << "loading from: " << dirname << endl;
auto dir = g_dir_open(dirname, 0, nullptr);
if(dir != nullptr) {
for(;;) {
const char* name = g_dir_read_name(dir);
if(name == nullptr) {
break;
}
// found a file in file-manager/actions dir, get its full path
CStrPtr full_path{g_build_filename(dirname, name, nullptr)};
// stdout.printf("\nfound %s\n", full_path);
// see if it's a sub dir
if(g_file_test(full_path.get(), G_FILE_TEST_IS_DIR)) {
// load sub dirs recursively
CStrPtr new_id_prefix;
if(id_prefix) {
new_id_prefix = CStrPtr{g_strconcat(id_prefix, "-", name, nullptr)};
}
load_actions_from_dir(full_path.get(), id_prefix ? new_id_prefix.get() : name);
}
else if(g_str_has_suffix(name, ".desktop")) {
CStrPtr new_id_prefix;
if(id_prefix) {
new_id_prefix = CStrPtr{g_strconcat(id_prefix, "-", name, nullptr)};
}
const char* id = id_prefix ? new_id_prefix.get() : name;
// ensure that it's not already in the cache
if(all_actions.find(id) == all_actions.cend()) {
auto kf = g_key_file_new();
if(g_key_file_load_from_file(kf, full_path.get(), G_KEY_FILE_NONE, nullptr)) {
auto type = CStrPtr{g_key_file_get_string(kf, "Desktop Entry", "Type", nullptr)};
if(!type) {
continue;
}
std::shared_ptr<FileActionObject> action;
if(strcmp(type.get(), "Action") == 0) {
action = static_pointer_cast<FileActionObject>(make_shared<FileAction>(kf));
// stdout.printf("load action: %s\n", id);
}
else if(strcmp(type.get(), "Menu") == 0) {
action = static_pointer_cast<FileActionObject>(make_shared<FileActionMenu>(kf));
// stdout.printf("load menu: %s\n", id);
}
else {
continue;
}
action->setId(id);
all_actions.insert(make_pair(action->id.get(), action)); // add the id/action pair to hash table
// stdout.printf("add to cache %s\n", id);
}
g_key_file_free(kf);
}
else {
// stdout.printf("cache found for action: %s\n", id);
}
}
}
g_dir_close(dir);
}
}
void file_actions_set_desktop_env(const char* env) {
desktop_env = env;
}
static void load_all_actions() {
all_actions.clear();
auto dirs = g_get_system_data_dirs();
for(auto dir = dirs; *dir; ++dir) {
CStrPtr dir_path{g_build_filename(*dir, "file-manager/actions", nullptr)};
load_actions_from_dir(dir_path.get(), nullptr);
}
CStrPtr dir_path{g_build_filename(g_get_user_data_dir(), "file-manager/actions", nullptr)};
load_actions_from_dir(dir_path.get(), nullptr);
actions_loaded = true;
}
bool FileActionItem::compare_items(std::shared_ptr<const FileActionItem> a, std::shared_ptr<const FileActionItem> b)
{
// first get the list of level-zero item names (http://www.nautilus-actions.org/?q=node/377)
static QStringList itemNamesList;
static bool level_zero_checked = false;
if(!level_zero_checked) {
level_zero_checked = true;
auto level_zero = CStrPtr{g_build_filename(g_get_user_data_dir(),
"file-manager/actions/level-zero.directory", nullptr)};
if(g_file_test(level_zero.get(), G_FILE_TEST_IS_REGULAR)) {
GKeyFile* kf = g_key_file_new();
if(g_key_file_load_from_file(kf, level_zero.get(), G_KEY_FILE_NONE, nullptr)) {
auto itemsList = CStrArrayPtr{g_key_file_get_string_list(kf,
"Desktop Entry",
"ItemsList", nullptr, nullptr)};
if(itemsList) {
for(uint i = 0; i < g_strv_length(itemsList.get()); ++i) {
CStrPtr desktop_file_name{g_strconcat(itemsList.get()[i], ".desktop", nullptr)};
auto desktop_file = CStrPtr{g_build_filename(g_get_user_data_dir(),
"file-manager/actions",
desktop_file_name.get(), nullptr)};
GKeyFile* desktop_file_key = g_key_file_new();
if(g_key_file_load_from_file(desktop_file_key, desktop_file.get(), G_KEY_FILE_NONE, nullptr)) {
auto actionName = CStrPtr{g_key_file_get_string(desktop_file_key,
"Desktop Entry",
"Name", NULL)};
if(actionName) {
itemNamesList << QString::fromUtf8(actionName.get());
}
}
g_key_file_free(desktop_file_key);
}
}
}
g_key_file_free(kf);
}
}
if(!itemNamesList.isEmpty()) {
int first = itemNamesList.indexOf(QString::fromStdString(a->get_name()));
int second = itemNamesList.indexOf(QString::fromStdString(b->get_name()));
if(first > -1) {
if(second > -1) {
return (first < second);
}
else {
return true; // list items have priority
}
}
else if(second > -1) {
return false;
}
}
return (a->get_name().compare(b->get_name()) < 0);
}
FileActionItemList FileActionItem::get_actions_for_files(const FileInfoList& files) {
if(!actions_loaded) {
load_all_actions();
}
// Iterate over all actions to establish association between parent menu
// and children actions, and to find out toplevel ones which are not
// attached to any parent menu
for(auto& item : all_actions) {
auto& action_obj = item.second;
// stdout.printf("id = %s\n", action_obj.id);
if(action_obj->type == FileActionType::MENU) { // this is a menu
auto menu = static_pointer_cast<FileActionMenu>(action_obj);
// stdout.printf("menu: %s\n", menu.name);
// associate child items with menus
menu->cache_children(files, (const char**)menu->items_list.get());
}
}
// Output the menus
FileActionItemList items;
for(auto& item : all_actions) {
auto& action_obj = item.second;
// only output toplevel items here
if(action_obj->has_parent == false) { // this is a toplevel item
auto item = FileActionItem::fromActionObject(action_obj, files);
if(item != nullptr) {
items.push_back(item);
}
}
}
// cleanup temporary data cached during menu generation
for(auto& item : all_actions) {
auto& action_obj = item.second;
action_obj->has_parent = false;
if(action_obj->type == FileActionType::MENU) {
auto menu = static_pointer_cast<FileActionMenu>(action_obj);
menu->cached_children.clear();
}
}
std::sort(items.begin(), items.end(), compare_items);
return items;
}
} // namespace Fm

@ -1,156 +0,0 @@
#ifndef FILEACTION_H
#define FILEACTION_H
#include <glib.h>
#include <string>
#include "../core/fileinfo.h"
#include "fileactioncondition.h"
#include "fileactionprofile.h"
namespace Fm {
enum class FileActionType {
NONE,
ACTION,
MENU
};
enum FileActionTarget {
FILE_ACTION_TARGET_NONE,
FILE_ACTION_TARGET_CONTEXT = 1,
FILE_ACTION_TARGET_LOCATION = 1 << 1,
FILE_ACTION_TARGET_TOOLBAR = 1 << 2
};
class FileActionObject {
public:
explicit FileActionObject();
explicit FileActionObject(GKeyFile* kf);
virtual ~FileActionObject();
void setId(const char* _id) {
id = CStrPtr{g_strdup(_id)};
}
static bool is_plural_exec(const char* exec);
static std::string expand_str(const char* templ, const FileInfoList& files, bool for_display = false, std::shared_ptr<const FileInfo> first_file = nullptr);
FileActionType type;
CStrPtr id;
CStrPtr name;
CStrPtr tooltip;
CStrPtr icon;
CStrPtr desc;
bool enabled;
bool hidden;
CStrPtr suggested_shortcut;
std::unique_ptr<FileActionCondition> condition;
// values cached during menu generation
bool has_parent;
};
class FileAction: public FileActionObject {
public:
FileAction(GKeyFile* kf);
std::shared_ptr<FileActionProfile> match(const FileInfoList& files) const;
int target; // bitwise or of FileActionTarget
CStrPtr toolbar_label;
// FIXME: currently we don't support dynamic profiles
std::vector<std::shared_ptr<FileActionProfile>> profiles;
};
class FileActionMenu : public FileActionObject {
public:
FileActionMenu(GKeyFile* kf);
bool match(const FileInfoList &files) const;
// called during menu generation
void cache_children(const FileInfoList &files, const char** items_list);
CStrArrayPtr items_list;
// values cached during menu generation
std::vector<std::shared_ptr<FileActionObject>> cached_children;
};
class FileActionItem {
public:
static std::shared_ptr<FileActionItem> fromActionObject(std::shared_ptr<FileActionObject> action_obj, const FileInfoList &files);
FileActionItem(std::shared_ptr<FileAction> _action, std::shared_ptr<FileActionProfile> _profile, const FileInfoList& files);
FileActionItem(std::shared_ptr<FileActionMenu> menu, const FileInfoList& files);
FileActionItem(std::shared_ptr<FileActionObject> _action, const FileInfoList& files);
const std::string& get_name() const {
return name;
}
const std::string& get_desc() const {
return desc;
}
const std::string& get_icon() const {
return icon;
}
const char* get_id() const {
return action->id.get();
}
FileActionTarget get_target() const {
if(action->type == FileActionType::ACTION) {
return FileActionTarget(static_cast<FileAction*>(action.get())->target);
}
return FILE_ACTION_TARGET_CONTEXT;
}
bool is_menu() const {
return (action->type == FileActionType::MENU);
}
bool is_action() const {
return (action->type == FileActionType::ACTION);
}
bool launch(GAppLaunchContext *ctx, const FileInfoList &files, CStrPtr &output) const;
const std::vector<std::shared_ptr<const FileActionItem>>& get_sub_items() const {
return children;
}
static bool compare_items(std::shared_ptr<const FileActionItem> a, std::shared_ptr<const FileActionItem> b);
static std::vector<std::shared_ptr<const FileActionItem>> get_actions_for_files(const FileInfoList& files);
std::string name;
std::string desc;
std::string icon;
std::shared_ptr<FileActionObject> action;
std::shared_ptr<FileActionProfile> profile; // only used by action item
std::vector<std::shared_ptr<const FileActionItem>> children; // only used by menu
};
typedef std::vector<std::shared_ptr<const FileActionItem>> FileActionItemList;
} // namespace Fm
#endif // FILEACTION_H

@ -1,508 +0,0 @@
#include "fileactioncondition.h"
#include "fileaction.h"
#include <string>
using namespace std;
namespace Fm {
FileActionCondition::FileActionCondition(GKeyFile *kf, const char* group) {
only_show_in = CStrArrayPtr{g_key_file_get_string_list(kf, group, "OnlyShowIn", nullptr, nullptr)};
not_show_in = CStrArrayPtr{g_key_file_get_string_list(kf, group, "NotShowIn", nullptr, nullptr)};
try_exec = CStrPtr{g_key_file_get_string(kf, group, "TryExec", nullptr)};
show_if_registered = CStrPtr{g_key_file_get_string(kf, group, "ShowIfRegistered", nullptr)};
show_if_true = CStrPtr{g_key_file_get_string(kf, group, "ShowIfTrue", nullptr)};
show_if_running = CStrPtr{g_key_file_get_string(kf, group, "ShowIfRunning", nullptr)};
mime_types = CStrArrayPtr{g_key_file_get_string_list(kf, group, "MimeTypes", nullptr, nullptr)};
base_names = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Basenames", nullptr, nullptr)};
match_case = g_key_file_get_boolean(kf, group, "Matchcase", nullptr);
CStrPtr selection_count_str{g_key_file_get_string(kf, group, "SelectionCount", nullptr)};
if(selection_count_str != nullptr) {
switch(selection_count_str[0]) {
case '<':
case '>':
case '=':
selection_count_cmp = selection_count_str[0];
selection_count = atoi(selection_count_str.get() + 1);
break;
default:
selection_count_cmp = '>';
selection_count = 0;
break;
}
}
else {
selection_count_cmp = '>';
selection_count = 0;
}
schemes = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Schemes", nullptr, nullptr)};
folders = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Folders", nullptr, nullptr)};
auto caps = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Capabilities", nullptr, nullptr)};
// FIXME: implement Capabilities support
}
bool FileActionCondition::match_try_exec(const FileInfoList& files) {
if(try_exec != nullptr) {
// stdout.printf(" TryExec: %s\n", try_exec);
CStrPtr exec_path{g_find_program_in_path(FileActionObject::expand_str(try_exec.get(), files).c_str())};
if(!g_file_test(exec_path.get(), G_FILE_TEST_IS_EXECUTABLE)) {
return false;
}
}
return true;
}
bool FileActionCondition::match_show_if_registered(const FileInfoList& files) {
if(show_if_registered != nullptr) {
// stdout.printf(" ShowIfRegistered: %s\n", show_if_registered);
auto service = FileActionObject::expand_str(show_if_registered.get(), files);
// References:
// http://people.freedesktop.org/~david/eggdbus-20091014/eggdbus-interface-org.freedesktop.DBus.html#eggdbus-method-org.freedesktop.DBus.NameHasOwner
// glib source code: gio/tests/gdbus-names.c
auto con = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
auto result = g_dbus_connection_call_sync(con,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"NameHasOwner",
g_variant_new("(s)", service.c_str()),
g_variant_type_new("(b)"),
G_DBUS_CALL_FLAGS_NONE,
-1, nullptr, nullptr);
bool name_has_owner;
g_variant_get(result, "(b)", &name_has_owner);
g_variant_unref(result);
// stdout.printf("check if service: %s is in use: %d\n", service, (int)name_has_owner);
if(!name_has_owner) {
return false;
}
}
return true;
}
bool FileActionCondition::match_show_if_true(const FileInfoList& files) {
if(show_if_true != nullptr) {
auto cmd = FileActionObject::expand_str(show_if_true.get(), files);
int exit_status;
// FIXME: Process.spawn cannot handle shell commands. Use Posix.system() instead.
//if(!Process.spawn_command_line_sync(cmd, nullptr, nullptr, out exit_status)
// || exit_status != 0)
// return false;
exit_status = system(cmd.c_str());
if(exit_status != 0) {
return false;
}
}
return true;
}
bool FileActionCondition::match_show_if_running(const FileInfoList& files) {
if(show_if_running != nullptr) {
auto process_name = FileActionObject::expand_str(show_if_running.get(), files);
CStrPtr pgrep{g_find_program_in_path("pgrep")};
bool running = false;
// pgrep is not fully portable, but we don't have better options here
if(pgrep != nullptr) {
int exit_status;
// cmd = "$pgrep -x '$process_name'"
string cmd = pgrep.get();
cmd += " -x \'";
cmd += process_name;
cmd += "\'";
if(g_spawn_command_line_sync(cmd.c_str(), nullptr, nullptr, &exit_status, nullptr)) {
if(exit_status == 0) {
running = true;
}
}
}
if(!running) {
return false;
}
}
return true;
}
bool FileActionCondition::match_mime_type(const FileInfoList& files, const char* type, bool negated) {
// stdout.printf("match_mime_type: %s, neg: %d\n", type, (int)negated);
if(strcmp(type, "all/all") == 0 || strcmp(type, "*") == 0) {
return negated ? false : true;
}
else if(strcmp(type, "all/allfiles") == 0) {
// see if all fileinfos are files
if(negated) { // all fileinfos should not be files
for(auto& fi: files) {
if(!fi->isDir()) { // at least 1 of the fileinfos is a file.
return false;
}
}
}
else { // all fileinfos should be files
for(auto& fi: files) {
if(fi->isDir()) { // at least 1 of the fileinfos is a file.
return false;
}
}
}
}
else if(g_str_has_suffix(type, "/*")) {
// check if all are subtypes of allowed_type
string prefix{type};
prefix.erase(prefix.length() - 1); // remove the last char
if(negated) { // all files should not have the prefix
for(auto& fi: files) {
if(g_str_has_prefix(fi->mimeType()->name(), prefix.c_str())) {
return false;
}
}
}
else { // all files should have the prefix
for(auto& fi: files) {
if(!g_str_has_prefix(fi->mimeType()->name(), prefix.c_str())) {
return false;
}
}
}
}
else {
if(negated) { // all files should not be of the type
for(auto& fi: files) {
if(strcmp(fi->mimeType()->name(),type) == 0) {
// if(ContentType.is_a(type, fi.get_mime_type().get_type())) {
return false;
}
}
}
else { // all files should be of the type
for(auto& fi: files) {
// stdout.printf("get_type: %s, type: %s\n", fi.get_mime_type().get_type(), type);
if(strcmp(fi->mimeType()->name(),type) != 0) {
// if(!ContentType.is_a(type, fi.get_mime_type().get_type())) {
return false;
}
}
}
}
return true;
}
bool FileActionCondition::match_mime_types(const FileInfoList& files) {
if(mime_types != nullptr) {
bool allowed = false;
// FIXME: this is inefficient, but easier to implement
// check if all of the mime_types are allowed
for(auto mime_type = mime_types.get(); *mime_type; ++mime_type) {
const char* allowed_type = *mime_type;
const char* type;
bool negated;
if(allowed_type[0] == '!') {
type = allowed_type + 1;
negated = true;
}
else {
type = allowed_type;
negated = false;
}
if(negated) { // negated mime_type rules are ANDed
bool type_is_allowed = match_mime_type(files, type, negated);
if(!type_is_allowed) { // so any mismatch is not allowed
return false;
}
}
else { // other mime_type rules are ORed
// matching any one of the mime_type is enough
if(!allowed) { // if no rule is matched yet
allowed = match_mime_type(files, type, false);
}
}
}
return allowed;
}
return true;
}
bool FileActionCondition::match_base_name(const FileInfoList& files, const char* base_name, bool negated) {
// see if all files has the base_name
// FIXME: this is inefficient, some optimization is needed later
GPatternSpec* pattern;
if(match_case) {
pattern = g_pattern_spec_new(base_name);
}
else {
CStrPtr case_fold{g_utf8_casefold(base_name, -1)};
pattern = g_pattern_spec_new(case_fold.get()); // FIXME: is this correct?
}
for(auto& fi: files) {
const char* name = fi->name().c_str();
if(match_case) {
if(g_pattern_match_string(pattern, name)) {
// at least 1 file has the base_name
if(negated) {
return false;
}
}
else {
// at least 1 file does not has the scheme
if(!negated) {
return false;
}
}
}
else {
CStrPtr case_fold{g_utf8_casefold(name, -1)};
if(g_pattern_match_string(pattern, case_fold.get())) {
// at least 1 file has the base_name
if(negated) {
return false;
}
}
else {
// at least 1 file does not has the scheme
if(!negated) {
return false;
}
}
}
}
return true;
}
bool FileActionCondition::match_base_names(const FileInfoList& files) {
if(base_names != nullptr) {
bool allowed = false;
// FIXME: this is inefficient, but easier to implement
// check if all of the base_names are allowed
for(auto it = base_names.get(); *it; ++it) {
auto allowed_name = *it;
const char* name;
bool negated;
if(allowed_name[0] == '!') {
name = allowed_name + 1;
negated = true;
}
else {
name = allowed_name;
negated = false;
}
if(negated) { // negated base_name rules are ANDed
bool name_is_allowed = match_base_name(files, name, negated);
if(!name_is_allowed) { // so any mismatch is not allowed
return false;
}
}
else { // other base_name rules are ORed
// matching any one of the base_name is enough
if(!allowed) { // if no rule is matched yet
allowed = match_base_name(files, name, false);
}
}
}
return allowed;
}
return true;
}
bool FileActionCondition::match_scheme(const FileInfoList& files, const char* scheme, bool negated) {
// FIXME: this is inefficient, some optimization is needed later
// see if all files has the scheme
for(auto& fi: files) {
if(fi->path().hasUriScheme(scheme)) {
// at least 1 file has the scheme
if(negated) {
return false;
}
}
else {
// at least 1 file does not has the scheme
if(!negated) {
return false;
}
}
}
return true;
}
bool FileActionCondition::match_schemes(const FileInfoList& files) {
if(schemes != nullptr) {
bool allowed = false;
// FIXME: this is inefficient, but easier to implement
// check if all of the schemes are allowed
for(auto it = schemes.get(); *it; ++it) {
auto allowed_scheme = *it;
const char* scheme;
bool negated;
if(allowed_scheme[0] == '!') {
scheme = allowed_scheme + 1;
negated = true;
}
else {
scheme = allowed_scheme;
negated = false;
}
if(negated) { // negated scheme rules are ANDed
bool scheme_is_allowed = match_scheme(files, scheme, negated);
if(!scheme_is_allowed) { // so any mismatch is not allowed
return false;
}
}
else { // other scheme rules are ORed
// matching any one of the scheme is enough
if(!allowed) { // if no rule is matched yet
allowed = match_scheme(files, scheme, false);
}
}
}
return allowed;
}
return true;
}
bool FileActionCondition::match_folder(const FileInfoList& files, const char* folder, bool negated) {
// trailing /* should always be implied.
// FIXME: this is inefficient, some optimization is needed later
GPatternSpec* pattern;
if(g_str_has_suffix(folder, "/*")) {
pattern = g_pattern_spec_new(folder);
}
else {
auto pat_str = g_str_has_suffix(folder, "/") ? string(folder) + "*" // be tolerant
: string(folder) + "/*";
pattern = g_pattern_spec_new(pat_str.c_str());
}
for(auto& fi: files) {
auto dirname = fi->isDir() ? fi->path().toString() // also match "folder" itself
: fi->dirPath().toString();
// Since the pattern ends with "/*", if the directory path is equal to "folder",
// it should end with "/" to be found as a match. Adding "/" is always harmless.
auto path_str = string(dirname.get()) + "/";
if(g_pattern_match_string(pattern, path_str.c_str())) { // at least 1 file is in the folder
if(negated) {
return false;
}
}
else {
if(!negated) {
return false;
}
}
}
return true;
}
bool FileActionCondition::match_folders(const FileInfoList& files) {
if(folders != nullptr) {
bool allowed = false;
// FIXME: this is inefficient, but easier to implement
// check if all of the schemes are allowed
for(auto it = folders.get(); *it; ++it) {
auto allowed_folder = *it;
const char* folder;
bool negated;
if(allowed_folder[0] == '!') {
folder = allowed_folder + 1;
negated = true;
}
else {
folder = allowed_folder;
negated = false;
}
if(negated) { // negated folder rules are ANDed
bool folder_is_allowed = match_folder(files, folder, negated);
if(!folder_is_allowed) { // so any mismatch is not allowed
return false;
}
}
else { // other folder rules are ORed
// matching any one of the folder is enough
if(!allowed) { // if no rule is matched yet
allowed = match_folder(files, folder, false);
}
}
}
return allowed;
}
return true;
}
bool FileActionCondition::match_selection_count(const FileInfoList& files) {
const int n_files = files.size();
switch(selection_count_cmp) {
case '<':
if(n_files >= selection_count) {
return false;
}
break;
case '=':
if(n_files != selection_count) {
return false;
}
break;
case '>':
if(n_files <= selection_count) {
return false;
}
break;
}
return true;
}
bool FileActionCondition::match_capabilities(const FileInfoList& /*files*/) {
// TODO
return true;
}
bool FileActionCondition::match(const FileInfoList& files) {
// all of the condition are combined with AND
// So, if any one of the conditions is not matched, we quit.
// TODO: OnlyShowIn, NotShowIn
if(!match_try_exec(files)) {
return false;
}
if(!match_mime_types(files)) {
return false;
}
if(!match_base_names(files)) {
return false;
}
if(!match_selection_count(files)) {
return false;
}
if(!match_schemes(files)) {
return false;
}
if(!match_folders(files)) {
return false;
}
// TODO: Capabilities
// currently, due to limitations of Fm.FileInfo, this cannot
// be implemanted correctly.
if(!match_capabilities(files)) {
return false;
}
if(!match_show_if_registered(files)) {
return false;
}
if(!match_show_if_true(files)) {
return false;
}
if(!match_show_if_running(files)) {
return false;
}
return true;
}
} // namespace Fm

@ -1,123 +0,0 @@
#ifndef FILEACTIONCONDITION_H
#define FILEACTIONCONDITION_H
#include <glib.h>
#include "../core/gioptrs.h"
#include "../core/fileinfo.h"
namespace Fm {
// FIXME: we can use getgroups() to get groups of current process
// then, call stat() and stat.st_gid to handle capabilities
// in this way, we don't have to call euidaccess
enum class FileActionCapability {
OWNER = 0,
READABLE = 1 << 1,
WRITABLE = 1 << 2,
EXECUTABLE = 1 << 3,
LOCAL = 1 << 4
};
class FileActionCondition {
public:
explicit FileActionCondition(GKeyFile* kf, const char* group);
#if 0
bool match_base_name_(const FileInfoList& files, const char* allowed_base_name) {
// all files should match the base_name pattern.
bool allowed = true;
if(allowed_base_name.index_of_char('*') >= 0) {
string allowed_base_name_ci;
if(!match_case) {
allowed_base_name_ci = allowed_base_name.casefold(); // FIXME: is this ok?
allowed_base_name = allowed_base_name_ci;
}
var pattern = new PatternSpec(allowed_base_name);
foreach(unowned FileInfo fi in files) {
unowned string name = fi.get_name();
if(match_case) {
if(!pattern.match_string(name)) {
allowed = false;
break;
}
}
else {
if(!pattern.match_string(name.casefold())) {
allowed = false;
break;
}
}
}
}
else {
foreach(unowned FileInfo fi in files) {
unowned string name = fi.get_name();
if(match_case) {
if(allowed_base_name != name) {
allowed = false;
break;
}
}
else {
if(allowed_base_name.collate(name) != 0) {
allowed = false;
break;
}
}
}
}
return allowed;
}
#endif
bool match_try_exec(const FileInfoList& files);
bool match_show_if_registered(const FileInfoList& files);
bool match_show_if_true(const FileInfoList& files);
bool match_show_if_running(const FileInfoList& files);
bool match_mime_type(const FileInfoList& files, const char* type, bool negated);
bool match_mime_types(const FileInfoList& files);
bool match_base_name(const FileInfoList& files, const char* base_name, bool negated);
bool match_base_names(const FileInfoList& files);
static bool match_scheme(const FileInfoList& files, const char* scheme, bool negated);
bool match_schemes(const FileInfoList& files);
static bool match_folder(const FileInfoList& files, const char* folder, bool negated);
bool match_folders(const FileInfoList& files);
bool match_selection_count(const FileInfoList &files);
bool match_capabilities(const FileInfoList& files);
bool match(const FileInfoList& files);
CStrArrayPtr only_show_in;
CStrArrayPtr not_show_in;
CStrPtr try_exec;
CStrPtr show_if_registered;
CStrPtr show_if_true;
CStrPtr show_if_running;
CStrArrayPtr mime_types;
CStrArrayPtr base_names;
bool match_case;
char selection_count_cmp;
int selection_count;
CStrArrayPtr schemes;
CStrArrayPtr folders;
FileActionCapability capabilities;
};
}
#endif // FILEACTIONCONDITION_H

@ -1,124 +0,0 @@
#include "fileactionprofile.h"
#include "fileaction.h"
#include <QDebug>
using namespace std;
namespace Fm {
FileActionProfile::FileActionProfile(GKeyFile *kf, const char* profile_name) {
id = profile_name;
std::string group_name = "X-Action-Profile " + id;
name = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "Name", nullptr)};
exec = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "Exec", nullptr)};
// stdout.printf("id: %s, Exec: %s\n", id, exec);
path = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "Path", nullptr)};
auto s = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "ExecutionMode", nullptr)};
if(s) {
if(strcmp(s.get(), "Normal") == 0) {
exec_mode = FileActionExecMode::NORMAL;
}
else if(strcmp(s.get(), "Terminal") == 0) {
exec_mode = FileActionExecMode::TERMINAL;
}
else if(strcmp(s.get(), "Embedded") == 0) {
exec_mode = FileActionExecMode::EMBEDDED;
}
else if(strcmp(s.get(), "DisplayOutput") == 0) {
exec_mode = FileActionExecMode::DISPLAY_OUTPUT;
}
else {
exec_mode = FileActionExecMode::NORMAL;
}
}
else {
exec_mode = FileActionExecMode::NORMAL;
}
startup_notify = g_key_file_get_boolean(kf, group_name.c_str(), "StartupNotify", nullptr);
startup_wm_class = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "StartupWMClass", nullptr)};
exec_as = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "ExecuteAs", nullptr)};
condition = make_shared<FileActionCondition>(kf, group_name.c_str());
}
bool FileActionProfile::launch_once(GAppLaunchContext* /*ctx*/, std::shared_ptr<const FileInfo> first_file, const FileInfoList& files, CStrPtr& output) {
if(exec == nullptr) {
return false;
}
auto exec_cmd = FileActionObject::expand_str(exec.get(), files, false, first_file);
bool ret = false;
if(exec_mode == FileActionExecMode::DISPLAY_OUTPUT) {
int exit_status;
char* output_buf = nullptr;
ret = g_spawn_command_line_sync(exec_cmd.c_str(), &output_buf, nullptr, &exit_status, nullptr);
if(ret) {
ret = (exit_status == 0);
}
output = CStrPtr{output_buf};
}
else {
/*
AppInfoCreateFlags flags = AppInfoCreateFlags.NONE;
if(startup_notify)
flags |= AppInfoCreateFlags.SUPPORTS_STARTUP_NOTIFICATION;
if(exec_mode == FileActionExecMode::TERMINAL ||
exec_mode == FileActionExecMode::EMBEDDED)
flags |= AppInfoCreateFlags.NEEDS_TERMINAL;
GLib.AppInfo app = Fm.AppInfo.create_from_commandline(exec, nullptr, flags);
stdout.printf("Execute command line: %s\n\n", exec);
ret = app.launch(nullptr, ctx);
*/
// NOTE: we cannot use GAppInfo here since GAppInfo does
// command line parsing which involving %u, %f, and other
// code defined in desktop entry spec.
// This may conflict with DES EMA parameters.
// FIXME: so how to handle this cleaner?
// Maybe we should leave all %% alone and don't translate
// them to %. Then GAppInfo will translate them to %, not
// codes specified in DES.
ret = g_spawn_command_line_async(exec_cmd.c_str(), nullptr);
}
return ret;
}
bool FileActionProfile::launch(GAppLaunchContext* ctx, const FileInfoList& files, CStrPtr& output) {
bool plural_form = FileActionObject::is_plural_exec(exec.get());
bool ret;
if(plural_form) { // plural form command, handle all files at a time
ret = launch_once(ctx, files.front(), files, output);
}
else { // singular form command, run once for each file
GString* all_output = g_string_sized_new(1024);
bool show_output = false;
for(auto& fi: files) {
CStrPtr one_output;
launch_once(ctx, fi, files, one_output);
if(one_output) {
show_output = true;
// FIXME: how to handle multiple output std::strings properly?
g_string_append(all_output, one_output.get());
g_string_append(all_output, "\n");
}
}
if(show_output) {
output = CStrPtr{g_string_free(all_output, false)};
}
else {
g_string_free(all_output, true);
}
ret = true;
}
return ret;
}
bool FileActionProfile::match(FileInfoList files) {
// stdout.printf(" match profile: %s\n", id);
return condition->match(files);
}
}

@ -1,45 +0,0 @@
#ifndef FILEACTIONPROFILE_H
#define FILEACTIONPROFILE_H
#include <string>
#include <glib.h>
#include <gio/gio.h>
#include "../core/fileinfo.h"
#include "fileactioncondition.h"
namespace Fm {
enum class FileActionExecMode {
NORMAL,
TERMINAL,
EMBEDDED,
DISPLAY_OUTPUT
};
class FileActionProfile {
public:
explicit FileActionProfile(GKeyFile* kf, const char* profile_name);
bool launch_once(GAppLaunchContext* ctx, std::shared_ptr<const FileInfo> first_file, const FileInfoList& files, CStrPtr& output);
bool launch(GAppLaunchContext* ctx, const FileInfoList& files, CStrPtr& output);
bool match(FileInfoList files);
std::string id;
CStrPtr name;
CStrPtr exec;
CStrPtr path;
FileActionExecMode exec_mode;
bool startup_notify;
CStrPtr startup_wm_class;
CStrPtr exec_as;
std::shared_ptr<FileActionCondition> condition;
};
} // namespace Fm
#endif // FILEACTIONPROFILE_H

@ -1,233 +0,0 @@
/*
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "dirtreemodel.h"
#include "dirtreemodelitem.h"
#include <QDebug>
#include "core/fileinfojob.h"
namespace Fm {
DirTreeModel::DirTreeModel(QObject* parent):
QAbstractItemModel(parent),
showHidden_(false) {
}
DirTreeModel::~DirTreeModel() {
}
void DirTreeModel::addRoots(Fm::FilePathList rootPaths) {
auto job = new Fm::FileInfoJob{std::move(rootPaths)};
job->setAutoDelete(true);
connect(job, &Fm::FileInfoJob::finished, this, &DirTreeModel::onFileInfoJobFinished, Qt::BlockingQueuedConnection);
job->runAsync();
}
void DirTreeModel::onFileInfoJobFinished() {
auto job = static_cast<Fm::FileInfoJob*>(sender());
for(auto file: job->files()) {
addRoot(std::move(file));
}
}
// QAbstractItemModel implementation
Qt::ItemFlags DirTreeModel::flags(const QModelIndex& index) const {
DirTreeModelItem* item = itemFromIndex(index);
if(item && item->isPlaceHolder()) {
return Qt::ItemIsEnabled;
}
return QAbstractItemModel::flags(index);
}
QVariant DirTreeModel::data(const QModelIndex& index, int role) const {
if(!index.isValid() || index.column() > 1) {
return QVariant();
}
DirTreeModelItem* item = itemFromIndex(index);
if(item) {
auto info = item->fileInfo_;
switch(role) {
case Qt::ToolTipRole:
return QVariant(item->displayName_);
case Qt::DisplayRole:
return QVariant(item->displayName_);
case Qt::DecorationRole:
return QVariant(item->icon_);
case FileInfoRole: {
QVariant v;
v.setValue(info);
return v;
}
}
}
return QVariant();
}
int DirTreeModel::columnCount(const QModelIndex& /*parent*/) const {
return 1;
}
int DirTreeModel::rowCount(const QModelIndex& parent) const {
if(!parent.isValid()) {
return rootItems_.size();
}
DirTreeModelItem* item = itemFromIndex(parent);
if(item) {
return item->children_.size();
}
return 0;
}
QModelIndex DirTreeModel::parent(const QModelIndex& child) const {
DirTreeModelItem* item = itemFromIndex(child);
if(item && item->parent_) {
item = item->parent_; // go to parent item
if(item) {
const auto& items = item->parent_ ? item->parent_->children_ : rootItems_;
auto it = std::find(items.cbegin(), items.cend(), item);
if(it != items.cend()) {
int row = it - items.cbegin();
return createIndex(row, 0, (void*)item);
}
}
}
return QModelIndex();
}
QModelIndex DirTreeModel::index(int row, int column, const QModelIndex& parent) const {
if(row >= 0 && column >= 0 && column == 0) {
if(!parent.isValid()) { // root items
if(static_cast<size_t>(row) < rootItems_.size()) {
const DirTreeModelItem* item = rootItems_.at(row);
return createIndex(row, column, (void*)item);
}
}
else { // child items
DirTreeModelItem* parentItem = itemFromIndex(parent);
if(static_cast<size_t>(row) < parentItem->children_.size()) {
const DirTreeModelItem* item = parentItem->children_.at(row);
return createIndex(row, column, (void*)item);
}
}
}
return QModelIndex(); // invalid index
}
bool DirTreeModel::hasChildren(const QModelIndex& parent) const {
DirTreeModelItem* item = itemFromIndex(parent);
return item ? !item->isPlaceHolder() : true;
}
QModelIndex DirTreeModel::indexFromItem(DirTreeModelItem* item) const {
Q_ASSERT(item);
const auto& items = item->parent_ ? item->parent_->children_ : rootItems_;
auto it = std::find(items.cbegin(), items.cend(), item);
if(it != items.cend()) {
int row = it - items.cbegin();
return createIndex(row, 0, (void*)item);
}
return QModelIndex();
}
// public APIs
QModelIndex DirTreeModel::addRoot(std::shared_ptr<const Fm::FileInfo> root) {
DirTreeModelItem* item = new DirTreeModelItem(std::move(root), this);
int row = rootItems_.size();
beginInsertRows(QModelIndex(), row, row);
rootItems_.push_back(item);
// add_place_holder_child_item(model, item_l, nullptr, FALSE);
endInsertRows();
return createIndex(row, 0, (void*)item);
}
DirTreeModelItem* DirTreeModel::itemFromIndex(const QModelIndex& index) const {
return reinterpret_cast<DirTreeModelItem*>(index.internalPointer());
}
QModelIndex DirTreeModel::indexFromPath(const Fm::FilePath &path) const {
DirTreeModelItem* item = itemFromPath(path);
return item ? item->index() : QModelIndex();
}
DirTreeModelItem* DirTreeModel::itemFromPath(const Fm::FilePath &path) const {
for(DirTreeModelItem* const item : qAsConst(rootItems_)) {
if(item->fileInfo_ && path == item->fileInfo_->path()) {
return item;
}
else {
DirTreeModelItem* child = item->childFromPath(path, true);
if(child) {
return child;
}
}
}
return nullptr;
}
void DirTreeModel::loadRow(const QModelIndex& index) {
DirTreeModelItem* item = itemFromIndex(index);
Q_ASSERT(item);
if(item && !item->isPlaceHolder()) {
item->loadFolder();
}
}
void DirTreeModel::unloadRow(const QModelIndex& index) {
DirTreeModelItem* item = itemFromIndex(index);
if(item && !item->isPlaceHolder()) {
item->unloadFolder();
}
}
bool DirTreeModel::isLoaded(const QModelIndex& index) {
DirTreeModelItem* item = itemFromIndex(index);
return item ? item->loaded_ : false;
}
QIcon DirTreeModel::icon(const QModelIndex& index) {
DirTreeModelItem* item = itemFromIndex(index);
return item ? item->icon_ : QIcon();
}
std::shared_ptr<const Fm::FileInfo> DirTreeModel::fileInfo(const QModelIndex& index) {
DirTreeModelItem* item = itemFromIndex(index);
return item ? item->fileInfo_ : nullptr;
}
Fm::FilePath DirTreeModel::filePath(const QModelIndex& index) {
DirTreeModelItem* item = itemFromIndex(index);
return (item && item->fileInfo_) ? item->fileInfo_->path() : Fm::FilePath{};
}
QString DirTreeModel::dispName(const QModelIndex& index) {
DirTreeModelItem* item = itemFromIndex(index);
return item ? item->displayName_ : QString();
}
void DirTreeModel::setShowHidden(bool show_hidden) {
showHidden_ = show_hidden;
for(DirTreeModelItem* const item : qAsConst(rootItems_)) {
item->setShowHidden(show_hidden);
}
}
} // namespace Fm

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save