dakkar 2026.11.8.50__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dakkar-2026.11.8.50/LICENSE +5 -0
- dakkar-2026.11.8.50/MANIFEST.in +5 -0
- dakkar-2026.11.8.50/PKG-INFO +165 -0
- dakkar-2026.11.8.50/README.md +139 -0
- dakkar-2026.11.8.50/dakkar/__init__.py +8 -0
- dakkar-2026.11.8.50/dakkar/__main__.py +8 -0
- dakkar-2026.11.8.50/dakkar/application.py +180 -0
- dakkar-2026.11.8.50/dakkar/archive.py +26 -0
- dakkar-2026.11.8.50/dakkar/bookmarks.py +56 -0
- dakkar-2026.11.8.50/dakkar/browser.py +2603 -0
- dakkar-2026.11.8.50/dakkar/cli.py +135 -0
- dakkar-2026.11.8.50/dakkar/data/__init__.py +3 -0
- dakkar-2026.11.8.50/dakkar/data/io.github.wdbm.dakkar.desktop.in +10 -0
- dakkar-2026.11.8.50/dakkar/data/io.github.wdbm.dakkar.svg +11 -0
- dakkar-2026.11.8.50/dakkar/directory_tint.py +368 -0
- dakkar-2026.11.8.50/dakkar/expandable.py +527 -0
- dakkar-2026.11.8.50/dakkar/file_manager_service.py +126 -0
- dakkar-2026.11.8.50/dakkar/history.py +72 -0
- dakkar-2026.11.8.50/dakkar/launching.py +87 -0
- dakkar-2026.11.8.50/dakkar/local_search.py +205 -0
- dakkar-2026.11.8.50/dakkar/metadata.py +94 -0
- dakkar-2026.11.8.50/dakkar/models.py +180 -0
- dakkar-2026.11.8.50/dakkar/operations.py +759 -0
- dakkar-2026.11.8.50/dakkar/saved_searches.py +63 -0
- dakkar-2026.11.8.50/dakkar/search.py +249 -0
- dakkar-2026.11.8.50/dakkar/services.py +405 -0
- dakkar-2026.11.8.50/dakkar/terminal.py +30 -0
- dakkar-2026.11.8.50/dakkar/thumbnails.py +270 -0
- dakkar-2026.11.8.50/dakkar/undo.py +98 -0
- dakkar-2026.11.8.50/dakkar/window.py +2989 -0
- dakkar-2026.11.8.50/dakkar.egg-info/PKG-INFO +165 -0
- dakkar-2026.11.8.50/dakkar.egg-info/SOURCES.txt +40 -0
- dakkar-2026.11.8.50/dakkar.egg-info/dependency_links.txt +1 -0
- dakkar-2026.11.8.50/dakkar.egg-info/entry_points.txt +2 -0
- dakkar-2026.11.8.50/dakkar.egg-info/requires.txt +5 -0
- dakkar-2026.11.8.50/dakkar.egg-info/top_level.txt +1 -0
- dakkar-2026.11.8.50/install.sh +114 -0
- dakkar-2026.11.8.50/pyproject.toml +47 -0
- dakkar-2026.11.8.50/setup.cfg +4 -0
- dakkar-2026.11.8.50/tools/check_dependencies.py +35 -0
- dakkar-2026.11.8.50/tools/prepare_screenshot_directory.py +294 -0
- dakkar-2026.11.8.50/tools/render_install_assets.py +63 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dakkar
|
|
3
|
+
Version: 2026.11.8.50
|
|
4
|
+
Summary: Dakkar is a GNOME file browser with grid, list, tab and pane views.
|
|
5
|
+
Author-email: William Breaden Madden <wbm@protonmail.ch>
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/wdbm/dakkar
|
|
8
|
+
Project-URL: Repository, https://github.com/wdbm/dakkar.git
|
|
9
|
+
Project-URL: Issues, https://github.com/wdbm/dakkar/issues
|
|
10
|
+
Keywords: file-browser,file-manager,gnome,gtk,linux
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: X11 Applications :: GTK
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Topic :: Desktop Environment :: File Managers
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: docopt>=0.6.2
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: build>=1.2.2; extra == "dev"
|
|
24
|
+
Requires-Dist: twine>=6.0; extra == "dev"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Dakkar
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
Dakkar is a Python GNOME file browser with grid, list and expandable-list views, windows, tabs, panes, and optional position and colour ranking visualisations.
|
|
32
|
+
|
|
33
|
+
## Setup
|
|
34
|
+
|
|
35
|
+
```Bash
|
|
36
|
+
sudo apt install \
|
|
37
|
+
desktop-file-utils \
|
|
38
|
+
gir1.2-gtk-4.0 \
|
|
39
|
+
gir1.2-gdkpixbuf-2.0 \
|
|
40
|
+
gir1.2-gnomedesktop-4.0 \
|
|
41
|
+
python3-docopt \
|
|
42
|
+
python3-gi \
|
|
43
|
+
python3-setuptools \
|
|
44
|
+
python3-venv \
|
|
45
|
+
python3-wheel
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Optional: Tracker accelerates indexed filename searches. File Roller provides archive creation, extraction and encrypted archives and passcode prompts. GStreamer metadata bindings add audio and video properties.
|
|
49
|
+
|
|
50
|
+
```Bash
|
|
51
|
+
sudo apt install \
|
|
52
|
+
file-roller \
|
|
53
|
+
gir1.2-gstreamer-1.0 \
|
|
54
|
+
gir1.2-gst-plugins-base-1.0 \
|
|
55
|
+
gir1.2-tracker-3.0
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Recommended setup
|
|
59
|
+
|
|
60
|
+
From a source checkout, perform a user installation.
|
|
61
|
+
|
|
62
|
+
```Bash
|
|
63
|
+
./install.sh --user
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Alternatively, perform a system-wide installation.
|
|
67
|
+
|
|
68
|
+
```Bash
|
|
69
|
+
sudo ./install.sh --system
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Dependencies are checked, a virtual environment with access to GNOME bindings is created, and the programme is installed without downloading packages. Launcher, desktop entry and icon are also installed. This method is recommended because `pip` does not place desktop integration files at the appropriate user or system locations.
|
|
73
|
+
|
|
74
|
+
### PyPI setup
|
|
75
|
+
|
|
76
|
+
The programme can be installed from PyPI. A virtual environment with access to system site packages is required so that PyGObject and GNOME bindings remain visible.
|
|
77
|
+
|
|
78
|
+
```Bash
|
|
79
|
+
python3 -m venv --system-site-packages .venv
|
|
80
|
+
source .venv/bin/activate
|
|
81
|
+
pip install dakkar
|
|
82
|
+
dakkar --version
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This method does not install the desktop entry or application icon, and the distribution packages listed above are still required.
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
High-contrast borders are enabled by default (`--high-contrast`) and can be disabled (`--no-high-contrast`). A dark palette with blue selection colour is used by default; the GTK theme and colours can be inherited with `--system-theme`. Colour ranking is disabled by default and can be enabled with `--directory-tint`. Optional indexed filename search can be disabled with `--no-index`.
|
|
90
|
+
|
|
91
|
+
## Usage
|
|
92
|
+
|
|
93
|
+
|Cause |Effect |
|
|
94
|
+
|------------------------------|------------------------------------------------------|
|
|
95
|
+
|`Ctrl+F` |Search filenames below the displayed directory |
|
|
96
|
+
|`Ctrl+Shift+F` |Search filenames below Home |
|
|
97
|
+
|Printable character input |Search filenames using the selected scope |
|
|
98
|
+
|`Escape` during search |Clear the search and return to the directory view |
|
|
99
|
+
|`Ctrl+L` |Focus the editable location field |
|
|
100
|
+
|`Alt+Left` / `Alt+Right` |Navigate back or forward |
|
|
101
|
+
|Back / Forward keys |Navigate back or forward |
|
|
102
|
+
|Mouse buttons 8 / 9 |Navigate back or forward |
|
|
103
|
+
|Middle mouse button |Open a directory or place in a new tab, or open a file|
|
|
104
|
+
|Drag / `Ctrl`+drag |Move items to the destination / copy them instead |
|
|
105
|
+
|`Alt+Up` / `Alt+Home` |Open the parent directory or Home |
|
|
106
|
+
|`Ctrl+1` / `Ctrl+2` / `Ctrl+3`|Use list, grid or expandable list view |
|
|
107
|
+
|`Ctrl+H` |Show or hide hidden files |
|
|
108
|
+
|`Ctrl+T` / `Ctrl+W` |Open or close a tab |
|
|
109
|
+
|`Ctrl+Shift+T` |Restore the most recently closed tab |
|
|
110
|
+
|`F9` |Show or hide `Places` |
|
|
111
|
+
|`Ctrl+N` |Open another window |
|
|
112
|
+
|`Ctrl+Shift+N` |Create a directory |
|
|
113
|
+
|`F2` |Rename the selected item |
|
|
114
|
+
|`Ctrl+C`, `Ctrl+X`, `Ctrl+V` |Copy, cut and paste |
|
|
115
|
+
|`Ctrl+D` |Duplicate selected items |
|
|
116
|
+
|`Ctrl+Z` / `Ctrl+Shift+Z` |Undo or redo a safe session operation |
|
|
117
|
+
|`Delete` / `Shift+Delete` |Move to Trash or permanently delete |
|
|
118
|
+
|`Alt+Return` |Show properties |
|
|
119
|
+
|`Ctrl+.` |Open the displayed local directory in a terminal |
|
|
120
|
+
|`F5` or `Ctrl+R` |Refresh |
|
|
121
|
+
|`F1` |Show keyboard shortcuts |
|
|
122
|
+
|
|
123
|
+
## Behaviour
|
|
124
|
+
|
|
125
|
+
### Places, paths and tabs
|
|
126
|
+
|
|
127
|
+
GTK bookmark files are read from the GTK 4 and GTK 3 configuration directories. A place can be opened in a new tab with the middle mouse button. The place context menu provides `Open in new tab` and `Open in new pane` when available, `Open in new window` and `Open in terminal`. The button beside the `Places` heading hides the panel; `F9` and `Show or hide Places` restore the previous panel width. Each breadcrumb path button provides the same directory actions.
|
|
128
|
+
|
|
129
|
+
Two tabbed panes can be displayed side by side. `Open in new pane` creates a second notebook at the right and transfers focus without moving existing tabs. New tabs and window-level navigation target the focused pane. Closing the final pane tab collapses the split. Tab labels use end ellipsisation when space is insufficient, and hovering reveals the full directory name. A tab context menu provides `Duplicate tab`, `Open in new pane` when available, `Open in new window`, `Open in terminal`, `Copy location`, `Refresh` and `Close tab` for the targeted tab.
|
|
130
|
+
|
|
131
|
+
`Ctrl+Shift+T` restores tabs in most-recently-closed order, including navigation history, pane and tab position, view, ordering, hidden-file state, grid settings, filters and colour-ranking configuration.
|
|
132
|
+
|
|
133
|
+
### Search and views
|
|
134
|
+
|
|
135
|
+
Search is restricted to filenames. Printable character input at the directory view is forwarded to `Search filenames`, providing type-ahead search without interfering with editable controls. The first sorted match is selected automatically when results begin to appear; later asynchronous batches do not overwrite a subsequent selection. `Escape` clears the query and returns focus to the directory view. `Filters` restricts loaded items and search matches by MIME family and modified, accessed or created date. Changes are staged until `Apply` is selected; selecting elsewhere closes the panel without applying them. Filters are retained by each tab. Searches can be saved and reopened through `Saved`. Direct global search is restricted to the home directory when the system index service is unavailable.
|
|
136
|
+
|
|
137
|
+
`Expandable list view` presents native disclosure controls beside directories. Expanding a row loads its immediate children asynchronously, and nested directories may be expanded to arbitrary depth. Several branches can load concurrently and each branch is monitored for external changes. Sorting, filters, selection status, context menus, drag-and-drop and file operations apply to nested rows. Expanded URIs are restored across refreshes and filter changes. Search results remain a flat list until the query is cleared.
|
|
138
|
+
|
|
139
|
+
Directory loading presents a small initial batch before processing larger batches, while sorting is distributed across GTK main-loop turns. File-monitor and completed-operation updates add, replace or remove only affected items in root and expanded views. Manual refresh preserves the displayed model while a background snapshot is reconciled. Up to ten recently visited directory snapshots are retained by each tab so that Back, Forward and repeat navigation can present cached contents immediately. Thumbnail requests belong to visible cells, prioritise newly visible items, discard obsolete callbacks after scrolling or navigation, and use a bounded in-memory cache.
|
|
140
|
+
|
|
141
|
+
Newly-opened directories start at the top of each view. The first item is maintained as the scrolling anchor during asynchronous enumeration and layout. Returning directly to a parent with `Back` selects and scrolls to the directory which has just been left after the parent has loaded.
|
|
142
|
+
|
|
143
|
+
### File operations
|
|
144
|
+
|
|
145
|
+
Selected files can be dragged between directories or exchanged with applications supporting the GTK file-list transfer type. Dropping over a directory targets that directory; dropping over the view background targets the displayed location. Move is preferred, while holding `Ctrl` requests Copy. Drag operations use the same recursive merge and conflict handling as clipboard operations.
|
|
146
|
+
|
|
147
|
+
`Paste`, drag-and-drop, `Copy to…` and `Move to…` share recursive conflict handling. Same-named directories may be merged or skipped. A merge descends into both trees and asks separately about nested conflicts. File conflicts show source and destination sizes and modification times and provide `Cancel`, `Replace`, `Skip` and a selectable destination name. Decisions may be applied to subsequent applicable conflicts. A skipped file remains at the destination during a copy and at the source during a move. `Duplicate` creates names such as `file (copy)`.
|
|
148
|
+
|
|
149
|
+
The directory-view context menu is adapted to its target and selection. Directory-only actions are omitted for files. `Copy name` copies each selected stored filename or directory name, while `Copy path` copies each complete native path, or the URI of a non-local item; multiple values are separated by line breaks. `Open` groups selected files by their default application and launches each application once with its complete group. `Open with…` lists applications advertised for every selected content type and passes the complete selection in one launch. A middle click opens a directory in a focused new tab or performs the normal `Open` action for a file. Filename-search results also provide `Open item location` variants which reveal the original result at its parent directory.
|
|
150
|
+
|
|
151
|
+
A local executable file provides `Run as program…`. The safety dialogue offers `Run in background` or `Run in terminal`. Both modes use the executable parent directory as the working directory. The terminal is launched with the desktop startup-notification context and originating input timestamp so that focus can be transferred without minimising.
|
|
152
|
+
|
|
153
|
+
The main and background menus provide `New directory…` and `New file…`; the editable defaults are `directory` and `file`. A newly created directory is selected after the refreshed view has loaded it. `Open in terminal` targets a selected directory or the displayed directory when requested from the view background. `Paste` is shown in context menus only when the clipboard contains files.
|
|
154
|
+
|
|
155
|
+
Archive actions are provided by the item context menu. The selected format is conveyed to File Roller by the filename suffix, and File Roller handles password dialogues for encrypted archives.
|
|
156
|
+
|
|
157
|
+
Undo and redo are session-only and safely reverse rename, creation of an empty directory, and creation of an empty file. Copy, move, drag-and-drop transfer, duplication, Trash, permanent deletion and externally handled archive operations install an undo barrier. Requesting `Undo` after one of those operations displays a dialogue explaining that it cannot be undone safely. Older history is discarded so that an inverse is not applied out of chronological order.
|
|
158
|
+
|
|
159
|
+
### Metadata and status
|
|
160
|
+
|
|
161
|
+
Mounted volumes are shown in `Places` with eject and unmount controls. Filesystem type, capacity and free space are reported asynchronously by the status line when supplied by the location.
|
|
162
|
+
|
|
163
|
+
When items are selected, the status line immediately reports their count and the known size of selected files. If directories remain selected briefly, recursive logical size is calculated by a cancellable background worker and progress is reported until the combined total is available. The delay avoids starting a traversal on the first click of a double-click. Unselected directories are not inspected by the selection-size worker, symbolic links are not followed, and changed selection cancels superseded work. Unreadable directory contents are identified beside the partial total.
|
|
164
|
+
|
|
165
|
+
Direct directory item counts are calculated away from the GTK thread and shown in all three views without collecting recursive sizes or modification times. Their calculation is deferred briefly after a directory has been enumerated and is restricted to directory items which have become visible, so that unseen directories do not compete with the initial view. Results are delivered to the interface in bounded batches without replacing model items. Recursive inspection starts promptly only while `Size` or `Deep directory modification` is selected for sorting, or while an enabled colour-ranking channel requests either measure. Inspection is cancelled when no active selection requires it. Collected metadata is reused when order is reversed or the requested recursive measure changes, including for loaded expandable branches.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Dakkar
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Dakkar is a Python GNOME file browser with grid, list and expandable-list views, windows, tabs, panes, and optional position and colour ranking visualisations.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
```Bash
|
|
10
|
+
sudo apt install \
|
|
11
|
+
desktop-file-utils \
|
|
12
|
+
gir1.2-gtk-4.0 \
|
|
13
|
+
gir1.2-gdkpixbuf-2.0 \
|
|
14
|
+
gir1.2-gnomedesktop-4.0 \
|
|
15
|
+
python3-docopt \
|
|
16
|
+
python3-gi \
|
|
17
|
+
python3-setuptools \
|
|
18
|
+
python3-venv \
|
|
19
|
+
python3-wheel
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Optional: Tracker accelerates indexed filename searches. File Roller provides archive creation, extraction and encrypted archives and passcode prompts. GStreamer metadata bindings add audio and video properties.
|
|
23
|
+
|
|
24
|
+
```Bash
|
|
25
|
+
sudo apt install \
|
|
26
|
+
file-roller \
|
|
27
|
+
gir1.2-gstreamer-1.0 \
|
|
28
|
+
gir1.2-gst-plugins-base-1.0 \
|
|
29
|
+
gir1.2-tracker-3.0
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Recommended setup
|
|
33
|
+
|
|
34
|
+
From a source checkout, perform a user installation.
|
|
35
|
+
|
|
36
|
+
```Bash
|
|
37
|
+
./install.sh --user
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Alternatively, perform a system-wide installation.
|
|
41
|
+
|
|
42
|
+
```Bash
|
|
43
|
+
sudo ./install.sh --system
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Dependencies are checked, a virtual environment with access to GNOME bindings is created, and the programme is installed without downloading packages. Launcher, desktop entry and icon are also installed. This method is recommended because `pip` does not place desktop integration files at the appropriate user or system locations.
|
|
47
|
+
|
|
48
|
+
### PyPI setup
|
|
49
|
+
|
|
50
|
+
The programme can be installed from PyPI. A virtual environment with access to system site packages is required so that PyGObject and GNOME bindings remain visible.
|
|
51
|
+
|
|
52
|
+
```Bash
|
|
53
|
+
python3 -m venv --system-site-packages .venv
|
|
54
|
+
source .venv/bin/activate
|
|
55
|
+
pip install dakkar
|
|
56
|
+
dakkar --version
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This method does not install the desktop entry or application icon, and the distribution packages listed above are still required.
|
|
60
|
+
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
High-contrast borders are enabled by default (`--high-contrast`) and can be disabled (`--no-high-contrast`). A dark palette with blue selection colour is used by default; the GTK theme and colours can be inherited with `--system-theme`. Colour ranking is disabled by default and can be enabled with `--directory-tint`. Optional indexed filename search can be disabled with `--no-index`.
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
|Cause |Effect |
|
|
68
|
+
|------------------------------|------------------------------------------------------|
|
|
69
|
+
|`Ctrl+F` |Search filenames below the displayed directory |
|
|
70
|
+
|`Ctrl+Shift+F` |Search filenames below Home |
|
|
71
|
+
|Printable character input |Search filenames using the selected scope |
|
|
72
|
+
|`Escape` during search |Clear the search and return to the directory view |
|
|
73
|
+
|`Ctrl+L` |Focus the editable location field |
|
|
74
|
+
|`Alt+Left` / `Alt+Right` |Navigate back or forward |
|
|
75
|
+
|Back / Forward keys |Navigate back or forward |
|
|
76
|
+
|Mouse buttons 8 / 9 |Navigate back or forward |
|
|
77
|
+
|Middle mouse button |Open a directory or place in a new tab, or open a file|
|
|
78
|
+
|Drag / `Ctrl`+drag |Move items to the destination / copy them instead |
|
|
79
|
+
|`Alt+Up` / `Alt+Home` |Open the parent directory or Home |
|
|
80
|
+
|`Ctrl+1` / `Ctrl+2` / `Ctrl+3`|Use list, grid or expandable list view |
|
|
81
|
+
|`Ctrl+H` |Show or hide hidden files |
|
|
82
|
+
|`Ctrl+T` / `Ctrl+W` |Open or close a tab |
|
|
83
|
+
|`Ctrl+Shift+T` |Restore the most recently closed tab |
|
|
84
|
+
|`F9` |Show or hide `Places` |
|
|
85
|
+
|`Ctrl+N` |Open another window |
|
|
86
|
+
|`Ctrl+Shift+N` |Create a directory |
|
|
87
|
+
|`F2` |Rename the selected item |
|
|
88
|
+
|`Ctrl+C`, `Ctrl+X`, `Ctrl+V` |Copy, cut and paste |
|
|
89
|
+
|`Ctrl+D` |Duplicate selected items |
|
|
90
|
+
|`Ctrl+Z` / `Ctrl+Shift+Z` |Undo or redo a safe session operation |
|
|
91
|
+
|`Delete` / `Shift+Delete` |Move to Trash or permanently delete |
|
|
92
|
+
|`Alt+Return` |Show properties |
|
|
93
|
+
|`Ctrl+.` |Open the displayed local directory in a terminal |
|
|
94
|
+
|`F5` or `Ctrl+R` |Refresh |
|
|
95
|
+
|`F1` |Show keyboard shortcuts |
|
|
96
|
+
|
|
97
|
+
## Behaviour
|
|
98
|
+
|
|
99
|
+
### Places, paths and tabs
|
|
100
|
+
|
|
101
|
+
GTK bookmark files are read from the GTK 4 and GTK 3 configuration directories. A place can be opened in a new tab with the middle mouse button. The place context menu provides `Open in new tab` and `Open in new pane` when available, `Open in new window` and `Open in terminal`. The button beside the `Places` heading hides the panel; `F9` and `Show or hide Places` restore the previous panel width. Each breadcrumb path button provides the same directory actions.
|
|
102
|
+
|
|
103
|
+
Two tabbed panes can be displayed side by side. `Open in new pane` creates a second notebook at the right and transfers focus without moving existing tabs. New tabs and window-level navigation target the focused pane. Closing the final pane tab collapses the split. Tab labels use end ellipsisation when space is insufficient, and hovering reveals the full directory name. A tab context menu provides `Duplicate tab`, `Open in new pane` when available, `Open in new window`, `Open in terminal`, `Copy location`, `Refresh` and `Close tab` for the targeted tab.
|
|
104
|
+
|
|
105
|
+
`Ctrl+Shift+T` restores tabs in most-recently-closed order, including navigation history, pane and tab position, view, ordering, hidden-file state, grid settings, filters and colour-ranking configuration.
|
|
106
|
+
|
|
107
|
+
### Search and views
|
|
108
|
+
|
|
109
|
+
Search is restricted to filenames. Printable character input at the directory view is forwarded to `Search filenames`, providing type-ahead search without interfering with editable controls. The first sorted match is selected automatically when results begin to appear; later asynchronous batches do not overwrite a subsequent selection. `Escape` clears the query and returns focus to the directory view. `Filters` restricts loaded items and search matches by MIME family and modified, accessed or created date. Changes are staged until `Apply` is selected; selecting elsewhere closes the panel without applying them. Filters are retained by each tab. Searches can be saved and reopened through `Saved`. Direct global search is restricted to the home directory when the system index service is unavailable.
|
|
110
|
+
|
|
111
|
+
`Expandable list view` presents native disclosure controls beside directories. Expanding a row loads its immediate children asynchronously, and nested directories may be expanded to arbitrary depth. Several branches can load concurrently and each branch is monitored for external changes. Sorting, filters, selection status, context menus, drag-and-drop and file operations apply to nested rows. Expanded URIs are restored across refreshes and filter changes. Search results remain a flat list until the query is cleared.
|
|
112
|
+
|
|
113
|
+
Directory loading presents a small initial batch before processing larger batches, while sorting is distributed across GTK main-loop turns. File-monitor and completed-operation updates add, replace or remove only affected items in root and expanded views. Manual refresh preserves the displayed model while a background snapshot is reconciled. Up to ten recently visited directory snapshots are retained by each tab so that Back, Forward and repeat navigation can present cached contents immediately. Thumbnail requests belong to visible cells, prioritise newly visible items, discard obsolete callbacks after scrolling or navigation, and use a bounded in-memory cache.
|
|
114
|
+
|
|
115
|
+
Newly-opened directories start at the top of each view. The first item is maintained as the scrolling anchor during asynchronous enumeration and layout. Returning directly to a parent with `Back` selects and scrolls to the directory which has just been left after the parent has loaded.
|
|
116
|
+
|
|
117
|
+
### File operations
|
|
118
|
+
|
|
119
|
+
Selected files can be dragged between directories or exchanged with applications supporting the GTK file-list transfer type. Dropping over a directory targets that directory; dropping over the view background targets the displayed location. Move is preferred, while holding `Ctrl` requests Copy. Drag operations use the same recursive merge and conflict handling as clipboard operations.
|
|
120
|
+
|
|
121
|
+
`Paste`, drag-and-drop, `Copy to…` and `Move to…` share recursive conflict handling. Same-named directories may be merged or skipped. A merge descends into both trees and asks separately about nested conflicts. File conflicts show source and destination sizes and modification times and provide `Cancel`, `Replace`, `Skip` and a selectable destination name. Decisions may be applied to subsequent applicable conflicts. A skipped file remains at the destination during a copy and at the source during a move. `Duplicate` creates names such as `file (copy)`.
|
|
122
|
+
|
|
123
|
+
The directory-view context menu is adapted to its target and selection. Directory-only actions are omitted for files. `Copy name` copies each selected stored filename or directory name, while `Copy path` copies each complete native path, or the URI of a non-local item; multiple values are separated by line breaks. `Open` groups selected files by their default application and launches each application once with its complete group. `Open with…` lists applications advertised for every selected content type and passes the complete selection in one launch. A middle click opens a directory in a focused new tab or performs the normal `Open` action for a file. Filename-search results also provide `Open item location` variants which reveal the original result at its parent directory.
|
|
124
|
+
|
|
125
|
+
A local executable file provides `Run as program…`. The safety dialogue offers `Run in background` or `Run in terminal`. Both modes use the executable parent directory as the working directory. The terminal is launched with the desktop startup-notification context and originating input timestamp so that focus can be transferred without minimising.
|
|
126
|
+
|
|
127
|
+
The main and background menus provide `New directory…` and `New file…`; the editable defaults are `directory` and `file`. A newly created directory is selected after the refreshed view has loaded it. `Open in terminal` targets a selected directory or the displayed directory when requested from the view background. `Paste` is shown in context menus only when the clipboard contains files.
|
|
128
|
+
|
|
129
|
+
Archive actions are provided by the item context menu. The selected format is conveyed to File Roller by the filename suffix, and File Roller handles password dialogues for encrypted archives.
|
|
130
|
+
|
|
131
|
+
Undo and redo are session-only and safely reverse rename, creation of an empty directory, and creation of an empty file. Copy, move, drag-and-drop transfer, duplication, Trash, permanent deletion and externally handled archive operations install an undo barrier. Requesting `Undo` after one of those operations displays a dialogue explaining that it cannot be undone safely. Older history is discarded so that an inverse is not applied out of chronological order.
|
|
132
|
+
|
|
133
|
+
### Metadata and status
|
|
134
|
+
|
|
135
|
+
Mounted volumes are shown in `Places` with eject and unmount controls. Filesystem type, capacity and free space are reported asynchronously by the status line when supplied by the location.
|
|
136
|
+
|
|
137
|
+
When items are selected, the status line immediately reports their count and the known size of selected files. If directories remain selected briefly, recursive logical size is calculated by a cancellable background worker and progress is reported until the combined total is available. The delay avoids starting a traversal on the first click of a double-click. Unselected directories are not inspected by the selection-size worker, symbolic links are not followed, and changed selection cancels superseded work. Unreadable directory contents are identified beside the partial total.
|
|
138
|
+
|
|
139
|
+
Direct directory item counts are calculated away from the GTK thread and shown in all three views without collecting recursive sizes or modification times. Their calculation is deferred briefly after a directory has been enumerated and is restricted to directory items which have become visible, so that unseen directories do not compete with the initial view. Results are delivered to the interface in bounded batches without replacing model items. Recursive inspection starts promptly only while `Size` or `Deep directory modification` is selected for sorting, or while an enabled colour-ranking channel requests either measure. Inspection is cancelled when no active selection requires it. Collected metadata is reused when order is reversed or the requested recursive measure changes, including for loaded expandable branches.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provide the GTK application.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import gi
|
|
8
|
+
|
|
9
|
+
gi.require_version("Gtk", "4.0")
|
|
10
|
+
gi.require_version("Gdk", "4.0")
|
|
11
|
+
from gi.repository import Gdk, Gio, GLib, Gtk
|
|
12
|
+
|
|
13
|
+
from .file_manager_service import FileManagerService
|
|
14
|
+
from .window import DakkarWindow
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
APP_ID = "io.github.wdbm.dakkar"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def application_css(system_theme: bool) -> bytes:
|
|
21
|
+
"""
|
|
22
|
+
Build structural CSS with either the Dakkar palette or GTK theme colours.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
palette = ""
|
|
26
|
+
border_colour = "currentColor" if system_theme else "#ffffff"
|
|
27
|
+
active_pane_colour = "currentColor" if system_theme else "#4f8cff"
|
|
28
|
+
if not system_theme:
|
|
29
|
+
palette = """
|
|
30
|
+
window, .view { background-color: #111318; color: #f3f4f6; }
|
|
31
|
+
.dakkar-sidebar { background-color: #171a20; }
|
|
32
|
+
.dakkar-status { color: #c7cad1; }
|
|
33
|
+
.dakkar-grid-cell:selected { background-color: #0033a0; }
|
|
34
|
+
"""
|
|
35
|
+
return f"""
|
|
36
|
+
{palette}
|
|
37
|
+
.dakkar-pathbar {{ padding: 4px 8px; }}
|
|
38
|
+
.dakkar-status {{ padding: 5px 10px; }}
|
|
39
|
+
.dakkar-grid-cell {{ padding: 8px; border-radius: 8px; }}
|
|
40
|
+
.dakkar-grid-label {{ font-size: 0.92em; }}
|
|
41
|
+
.dakkar-high-contrast button {{
|
|
42
|
+
border: 1px solid {border_colour};
|
|
43
|
+
}}
|
|
44
|
+
.dakkar-high-contrast notebook > header > tabs > tab {{
|
|
45
|
+
border: 1px solid {border_colour};
|
|
46
|
+
}}
|
|
47
|
+
.dakkar-high-contrast .dakkar-notebook > header {{
|
|
48
|
+
border: 1px solid {border_colour};
|
|
49
|
+
}}
|
|
50
|
+
.dakkar-high-contrast .dakkar-file-area,
|
|
51
|
+
.dakkar-high-contrast .dakkar-navigation-area,
|
|
52
|
+
.dakkar-high-contrast .dakkar-places-panel,
|
|
53
|
+
.dakkar-high-contrast .dakkar-places-heading,
|
|
54
|
+
.dakkar-high-contrast .dakkar-status,
|
|
55
|
+
.dakkar-high-contrast .dakkar-location-entry,
|
|
56
|
+
.dakkar-high-contrast .dakkar-search-entry {{
|
|
57
|
+
border: 1px solid {border_colour};
|
|
58
|
+
}}
|
|
59
|
+
.dakkar-active-pane {{
|
|
60
|
+
box-shadow: inset 0 0 0 2px {active_pane_colour};
|
|
61
|
+
}}
|
|
62
|
+
""".encode("utf-8")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class DakkarApplication(Gtk.Application):
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
use_index: bool = True,
|
|
69
|
+
open_separate_windows: bool = False,
|
|
70
|
+
service_mode: bool = False,
|
|
71
|
+
high_contrast: bool = True,
|
|
72
|
+
directory_tint: bool = False,
|
|
73
|
+
system_theme: bool = False,
|
|
74
|
+
) -> None:
|
|
75
|
+
flags = Gio.ApplicationFlags.HANDLES_OPEN
|
|
76
|
+
if not service_mode:
|
|
77
|
+
flags |= Gio.ApplicationFlags.NON_UNIQUE
|
|
78
|
+
super().__init__(application_id=APP_ID, flags=flags)
|
|
79
|
+
self.use_index = use_index
|
|
80
|
+
self.open_separate_windows = open_separate_windows
|
|
81
|
+
self.high_contrast = high_contrast
|
|
82
|
+
self.directory_tint = directory_tint
|
|
83
|
+
self.system_theme = system_theme
|
|
84
|
+
self.file_manager_service: FileManagerService | None = None
|
|
85
|
+
|
|
86
|
+
def do_startup(self) -> None:
|
|
87
|
+
Gtk.Application.do_startup(self)
|
|
88
|
+
settings = Gtk.Settings.get_default()
|
|
89
|
+
if settings is not None and not self.system_theme:
|
|
90
|
+
settings.set_property("gtk-application-prefer-dark-theme", True)
|
|
91
|
+
display = Gdk.Display.get_default()
|
|
92
|
+
if display is not None:
|
|
93
|
+
css = Gtk.CssProvider()
|
|
94
|
+
css.load_from_data(application_css(self.system_theme))
|
|
95
|
+
Gtk.StyleContext.add_provider_for_display(
|
|
96
|
+
display,
|
|
97
|
+
css,
|
|
98
|
+
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
99
|
+
)
|
|
100
|
+
self._install_accelerators()
|
|
101
|
+
self.file_manager_service = FileManagerService(self)
|
|
102
|
+
self.file_manager_service.start()
|
|
103
|
+
|
|
104
|
+
def do_shutdown(self) -> None:
|
|
105
|
+
if self.file_manager_service is not None:
|
|
106
|
+
self.file_manager_service.stop()
|
|
107
|
+
self.file_manager_service = None
|
|
108
|
+
Gtk.Application.do_shutdown(self)
|
|
109
|
+
|
|
110
|
+
def _install_accelerators(self) -> None:
|
|
111
|
+
bindings = {
|
|
112
|
+
"win.new-tab": ["<Primary>t"],
|
|
113
|
+
"win.close-tab": ["<Primary>w"],
|
|
114
|
+
"win.restore-tab": ["<Primary><Shift>t"],
|
|
115
|
+
"win.new-window": ["<Primary>n"],
|
|
116
|
+
"win.back": ["<Alt>Left", "Back"],
|
|
117
|
+
"win.forward": ["<Alt>Right", "Forward"],
|
|
118
|
+
"win.up": ["<Alt>Up"],
|
|
119
|
+
"win.home": ["<Alt>Home"],
|
|
120
|
+
"win.refresh": ["F5", "<Primary>r"],
|
|
121
|
+
"win.location": ["<Primary>l"],
|
|
122
|
+
"win.search": ["<Primary>f"],
|
|
123
|
+
"win.global-search": ["<Primary><Shift>f"],
|
|
124
|
+
"win.stop-search": ["Escape"],
|
|
125
|
+
"win.keyboard-shortcuts": ["F1"],
|
|
126
|
+
"win.show-hidden": ["<Primary>h"],
|
|
127
|
+
"win.toggle-places": ["F9"],
|
|
128
|
+
"win.list-view": ["<Primary>1"],
|
|
129
|
+
"win.grid-view": ["<Primary>2"],
|
|
130
|
+
"win.expandable-list-view": ["<Primary>3"],
|
|
131
|
+
"win.new-directory": ["<Primary><Shift>n"],
|
|
132
|
+
"win.rename": ["F2"],
|
|
133
|
+
"win.trash": ["Delete"],
|
|
134
|
+
"win.delete": ["<Shift>Delete"],
|
|
135
|
+
"win.copy": ["<Primary>c"],
|
|
136
|
+
"win.cut": ["<Primary>x"],
|
|
137
|
+
"win.paste": ["<Primary>v"],
|
|
138
|
+
"win.duplicate": ["<Primary>d"],
|
|
139
|
+
"win.undo": ["<Primary>z"],
|
|
140
|
+
"win.redo": ["<Primary><Shift>z"],
|
|
141
|
+
"win.properties": ["<Alt>Return", "<Primary>i"],
|
|
142
|
+
"win.terminal": ["<Primary>period"],
|
|
143
|
+
"win.select-all": ["<Primary>a"],
|
|
144
|
+
"win.zoom-in": ["<Primary>plus", "<Primary>equal"],
|
|
145
|
+
"win.zoom-out": ["<Primary>minus"],
|
|
146
|
+
}
|
|
147
|
+
for action, accelerators in bindings.items():
|
|
148
|
+
self.set_accels_for_action(action, accelerators)
|
|
149
|
+
|
|
150
|
+
def new_window(self, location: Gio.File | None = None) -> DakkarWindow:
|
|
151
|
+
window = DakkarWindow(
|
|
152
|
+
self,
|
|
153
|
+
use_index=self.use_index,
|
|
154
|
+
high_contrast=self.high_contrast,
|
|
155
|
+
directory_tint=self.directory_tint,
|
|
156
|
+
)
|
|
157
|
+
window.add_tab(location or Gio.File.new_for_path(GLib.get_home_dir()))
|
|
158
|
+
window.present()
|
|
159
|
+
return window
|
|
160
|
+
|
|
161
|
+
def do_activate(self) -> None:
|
|
162
|
+
self.new_window()
|
|
163
|
+
|
|
164
|
+
def do_open(
|
|
165
|
+
self,
|
|
166
|
+
files: list[Gio.File],
|
|
167
|
+
_count: int,
|
|
168
|
+
_hint: str,
|
|
169
|
+
) -> None:
|
|
170
|
+
locations = list(files)
|
|
171
|
+
if not locations:
|
|
172
|
+
self.do_activate()
|
|
173
|
+
return
|
|
174
|
+
if self.open_separate_windows:
|
|
175
|
+
for location in locations:
|
|
176
|
+
self.new_window(location)
|
|
177
|
+
return
|
|
178
|
+
window = self.new_window(locations[0])
|
|
179
|
+
for location in locations[1:]:
|
|
180
|
+
window.add_tab(location)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Launch the installed archive manager.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def launch_archive_manager(arguments: Sequence[str]) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Launch the archive manager with provider-specific arguments.
|
|
15
|
+
|
|
16
|
+
Raise `FileNotFoundError` with an installation hint when the configured
|
|
17
|
+
archive manager is unavailable. Propagate launch failures as `OSError`.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
executable_path = shutil.which("file-roller")
|
|
21
|
+
if executable_path is None:
|
|
22
|
+
raise FileNotFoundError(
|
|
23
|
+
"File Roller is required for archive creation and extraction. "
|
|
24
|
+
"Install the file-roller package."
|
|
25
|
+
)
|
|
26
|
+
subprocess.Popen([executable_path, *arguments])
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Read GTK bookmark files.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import os
|
|
10
|
+
from urllib.parse import unquote, urlparse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, slots=True)
|
|
14
|
+
class Bookmark:
|
|
15
|
+
uri: str
|
|
16
|
+
label: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def default_bookmark_paths() -> tuple[Path, ...]:
|
|
20
|
+
config_home = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"))
|
|
21
|
+
return (
|
|
22
|
+
config_home / "gtk-4.0" / "bookmarks",
|
|
23
|
+
config_home / "gtk-3.0" / "bookmarks",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _default_label(uri: str) -> str:
|
|
28
|
+
parsed = urlparse(uri)
|
|
29
|
+
if parsed.scheme == "file":
|
|
30
|
+
path = Path(unquote(parsed.path))
|
|
31
|
+
return path.name or str(path)
|
|
32
|
+
return unquote(parsed.path.rstrip("/").rsplit("/", 1)[-1]) or uri
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def parse_bookmark_line(line: str) -> Bookmark | None:
|
|
36
|
+
value = line.strip()
|
|
37
|
+
if not value or value.startswith("#"):
|
|
38
|
+
return None
|
|
39
|
+
uri, separator, label = value.partition(" ")
|
|
40
|
+
return Bookmark(uri=uri, label=label.strip() if separator else _default_label(uri))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_bookmarks(paths: tuple[Path, ...] | None = None) -> list[Bookmark]:
|
|
44
|
+
bookmarks: list[Bookmark] = []
|
|
45
|
+
seen: set[str] = set()
|
|
46
|
+
for path in paths or default_bookmark_paths():
|
|
47
|
+
try:
|
|
48
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
49
|
+
except (FileNotFoundError, OSError, UnicodeError):
|
|
50
|
+
continue
|
|
51
|
+
for line in lines:
|
|
52
|
+
bookmark = parse_bookmark_line(line)
|
|
53
|
+
if bookmark is not None and bookmark.uri not in seen:
|
|
54
|
+
seen.add(bookmark.uri)
|
|
55
|
+
bookmarks.append(bookmark)
|
|
56
|
+
return bookmarks
|