View previous topic :: View next topic |
Author |
Message |
frankenputer n00b
Joined: 09 Mar 2016 Posts: 26
|
Posted: Fri Jul 08, 2016 7:29 pm Post subject: [HOWTO] Extend Thunar with DIY plugins |
|
|
Pick the language you are most familiar with and you can extend Thunar the way you want to via plugins.
In this post I'll show how to create compression and extraction functions that use nothing, but the shell itself which Thunar will utilize to manipulate files. For the fun I'll use python to create and extract zip archives instead installing moar dependencies such as zip, unzip, thunar-archive-plugin, xarchiver/file-roller/ark.
Let's start with the compression functions.
Filename compress.zsh
Code: | compresstar() { tar --verbose --dereference --create --file\
`basename $1`.tar "$@" ;}
compressxz() { compresstar "$@"
xz --verbose --force -9 --extreme `basename $1.tar` ;}
compressgz() { compresstar "$@"
__run_parallel pigz gzip $1 ;}
compressbz() { compresstar "$@"
__run_parallel lbzip2 bzip2 $1 ;}
compresslz() { compresstar "$@"
xz --verbose --force -9 --extreme \
--format=lzma `basename $1.tar` ;}
compresslz4() { compresstar "$@"
lz4 -vf9 `basename $1.tar`
rm `basename $1.tar` ;}
compresslzo() { compresstar "$@"
lzop --verbose --force -9 `basename $1.tar`
rm `basename $1.tar` ;}
compresslrz() { compresstar "$@"
lrzip --verbose --force -f -L 9 `basename $1.tar`
rm `basename $1.tar` }
compress7z() { 7za a -mx=9 `basename $1`.7z "$@" ;}
compresszip() {
python2 -c"import os;from zipfile import ZipFile,ZIP_DEFLATED;
ufo_obj='"$1"'.split(' ');the_list=list();path_join=os.path.join;
norm='\033[0m';blue='\033[1;94m';magenta='\033[1;95m';
def zip_filez():
with ZipFile(os.path.basename(ufo_obj[0])+'.zip','a',ZIP_DEFLATED) as archive:
[archive.write(x) for x in the_list];
for x in the_list:
x=(x if not x.startswith(os.sep) else x.replace(os.sep,str(),1));
print(' {0}adding{1}: {2}{3}{1}'.format(magenta,norm,blue,x));
for x in ufo_obj:
if os.path.isdir(x):
for root,_,files in os.walk(x):
for z in files:
the_list.append(path_join(root,z));
else: the_list.append(x);
zip_filez();"
;} |
The extraction function, filename extract.zsh
Code: | extract() {
for xXx in "$@"
do
if [[ -f $xXx ]]
then
# --bzip2, --gzip, --xz, --lzma, --lzop
# are 'long' `tar' options standing for:
# extract the archive and filter it
# through program --xxx
case $xXx in
*.tar.bz2) __fucktard $xXx --bzip2 ;;
*.bz2) __fucktard $xXx bz2-orig ;;
*.t[zb]?*) __fucktard $xXx --bzip2 ;;
*.tar.gz) __fucktard $xXx --gzip ;;
*.gz) __fucktard $xXx gz-orig ;;
*.t[ag]z) __fucktard $xXx --gzip ;;
*.tz) __fucktard $xXx --gzip ;;
*.tar.xz) __fucktard $xXx --xz ;;
*.xz) __fucktard $xXx xz-orig ;;
*.txz) __fucktard $xXx --xz ;;
*.tpxz) __fucktard $xXx --xz ;;
*.lzma) __fucktard $xXx --lzma ;;
*.tlz) __fucktard $xXx --lzma ;;
*.tar) __fucktard $xXx tar-orig ;;
*.rar) __fucktard $xXx rar-orig ;;
*.zip) __fucktard $xXx zip-orig ;;
*.xpi) __fucktard $xXx zip-orig ;;
*.lz4) __fucktard $xXx lz4-orig ;;
*.lrz) __fucktard $xXx lrz-orig ;;
*.tar.lzo) __fucktard $xXx --lzop ;;
*.lzo) __fucktard $xXx lzo-orig ;;
*.Z) uncompress $xXx ;;
*.7z) __fucktard $xXx seven_zip ;;
*.exe) cabextract $xXx ;;
*) ;;
esac
fi
done
unset xXx
;} |
Non-interactive functions that will do most of the heavy work.
Filename diehard.zsh:
Code: | # compress.zsh
# pixz does not show any benefits
# pbzip2 is twice slower than lbzip2
__run_parallel() {
[[ -x "$(command -v $1)" ]] && local comp_prog=$1 \
|| local comp_prog=$2
${comp_prog} --verbose --force -9 `basename $3.tar`
;}
# extract.zsh
# The extraction function that does
# all the heavy lifting by using steroids
# and several other prohibited substances
__fucktard() {
[[ "$(dirname $1)" == "." ]] && local dir_name="${PWD}" \
|| local dir_name="$(dirname $1)"
local f_name="$(basename $1)"
local temp_one="$(mktemp --directory --tmpdir XXXXXXX)"
local temp_two="$(mktemp --directory --tmpdir=${temp_one} XXXXXXX)"
mv $1 "${temp_two}" && cd "${temp_two}"
case $2 in
bz2-orig) bunzip2 --verbose "${f_name}" ;;
gz-orig) gunzip --verbose "${f_name}" ;;
xz-orig) unxz --verbose "${f_name}" ;;
zip-orig) python2 -c"from zipfile import ZipFile;
with ZipFile('"${f_name}"', 'r') as archive:
print('\n'.join(' \033[1;95mextracted\033[0m: \033[1;94m{0}\033[0m'.format(x.filename)\
for x in archive.infolist()));archive.extractall()" ;;
seven_zip) 7z x "${f_name}" ;;
rar-orig) unrar x "${f_name}" ;;
lzop-orig) lzop --verbose --extract "${f_name}" ;;
lrz-orig) lrzuntar -v "${f_name}" ;;
tar-orig|lz4-orig)
if [[ "$2" == "lz4-orig" ]]
then
lz4 --verbose --decompress "${f_name}"
f_name="${f_name%%.lz4}"
fi
if [[ "${f_name}" == *.tar ]]
then
tar --skip-old-files --no-overwrite-dir \
--verbose --extract --file "${f_name}"
fi ;;
# filter the archive through program $2(--xxx)
*) tar --skip-old-files --no-overwrite-dir\
--verbose --extract $2 --file "${f_name}" ;;
esac
# The more scenarios I can think of, `mv'
# becomes true trouble maker. Let's leave
# python to deal most common issues (if any)
python2 -c"import os;from shutil import move,rmtree;
whos_here=os.listdir(os.getcwd());charge=os.path.join;
is_grenate=os.path.isfile;is_dynamite=os.path.isdir;defuse=os.remove;
for x in whos_here:
say_what=charge('"${dir_name}"',x);
if is_grenate(say_what): defuse(say_what);
if is_dynamite(say_what): rmtree(say_what);
[move(x,'"$dir_name"') for x in whos_here];"
cd "${dir_name}" && rm -rf "${temp_one}"
;} |
The plugin interface that invokes the archive creation and extraction.
Filename my_thunar_plugin.zsh:
Code: | #!/usr/bin/env zsh
files_to_source=(
'compress.zsh' 'extract.zsh'
'diehard.zsh'
)
for x in "${files_to_source[@]}"
do
source "$HOME/.config/zsh/functions/$x"
done
unset x files_to_source
case $1 in
gzf) shift; compressgz "$@" ;;
extr) shift; extract "$@" ;;
*) ;;
esac |
Open up Thunar, select Edit and then click Configure custom actions located at the very bottom.
A new window will be opened, click the + symbol to the right and add a new custom action. The first custom action that we will create is archive extraction.
In the Name field type Extract here, and in the Command field type:
Code: | zsh --exec $HOME/.config/misc/my_thunar_plugin.zsh extr %N |
Switch to Appearance tab and in the File Pattern field type:
Code: | *.tar.bz2;*.bz2;*.tz2;*.tbz2;*.tbz;*.tb2;*.tar.gz;*.gz;*.tgz;*.taz;*.tz;*.tar.xz;*.xz;*.txz;*.tpxz;*.lzma;*.tlz;*.rar;*.zip;*.xpi;*.tar;*.lz4;*.lrz;*.tar.lzo;*.lzo;*.Z;*.7z;*.exe |
Make sure to toggle the Other Files switch.
Now the second action - archive creation and compression. Pretty much the same steps with some exceptions.
Again click the + symbol, in the Name field type Compress to gzip and in the Command field type:
Code: | zsh --exec $HOME/.config/misc/my_thunar_plugin.zsh gzf %N |
Switch to Appearance tab and in the File Pattern field type *
and toggle all the switches.
That was it, re-open Thunar and you good to go.
Once you select single/multiple file(s) in Thunar and right click, these custom actions should appear.
This approach of writing Thunar plugins by using the shell itself allows you to invoke the functions on a daily basis as well use them with Thunar when you need to.
The use of /tmp also helps to speed up the extraction since it reads and writes only in the RAM without touching your drive during that time.
Since there is no GUI involved in here, the extraction is lightning fast, as the GUI programs have to first of all read the archive content before even the extraction to begin (the tiny window that moves the progress bar), which in most cases will cause twice slower extraction than using the shell alternatives presented here. |
|
Back to top |
|
|
frankenputer n00b
Joined: 09 Mar 2016 Posts: 26
|
Posted: Sat Jul 09, 2016 8:09 pm Post subject: |
|
|
I've had enough crashed sessions caused by just trying to rename some file/directory in Thunar.
It's not like this problem started since 1.6.10, it's been for awhile.
Decided that it's time to take action and replace the built-in renaming function. The new renaming function will sanitize the input. Only valid alphabets, numbers and some punctuation marks are allowed, everything else will be discarded.
Upstream repo https://github.com/wifiextender/no_thunar_rename_crash
Code: | /*
Copyright Aaron Caffrey https://github.com/wifiextender
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.
Compile with:
gcc -Wall -Wextra -O2 main.c -o rename `pkg-config --cflags --libs gtk+-3.0`
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <gtk/gtk.h>
#define POS_LEFT 1
#define POS_RIGHT 2
#define MAX_NAME_LEN 300
void try_to_rename(void);
void sanitize_name(char *);
void display_warning(const char *, const char *);
GtkWidget *window, *entry;
char orig[MAX_NAME_LEN];
int main(int argc, char *argv[]) {
if (argc == 1 || argc > 2) {
exit (EXIT_FAILURE);
}
snprintf (orig, MAX_NAME_LEN, "%s", argv[1]);
if (0 != access (orig, F_OK) || 0 != access (orig, W_OK)) {
exit (EXIT_FAILURE);
}
GtkWidget *grid, *entry_label, *ok_button;
gtk_init(&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW(window), "Rename");
gtk_container_set_border_width (GTK_CONTAINER(window), 6);
gtk_window_set_default_size (GTK_WINDOW(window), 250, 20);
grid = gtk_grid_new();
gtk_grid_set_row_spacing (GTK_GRID(grid), 7);
gtk_grid_set_column_spacing (GTK_GRID(grid), 5);
gtk_container_set_border_width (GTK_CONTAINER(grid), 2);
gtk_container_add (GTK_CONTAINER(window), grid);
entry_label = gtk_label_new ("New name:");
gtk_grid_attach (GTK_GRID(grid), entry_label, POS_LEFT, 1, 1, 1);
entry = gtk_entry_new();
gtk_entry_set_width_chars (GTK_ENTRY(entry), 1);
gtk_entry_set_text (GTK_ENTRY(entry), orig);
gtk_entry_set_max_length (GTK_ENTRY(entry), 200);
gtk_grid_attach (GTK_GRID(grid), entry, POS_RIGHT, 1, 1, 1);
ok_button = gtk_button_new_with_label ("OK");
g_signal_connect (G_OBJECT(ok_button), "clicked", G_CALLBACK(try_to_rename), NULL);
gtk_grid_attach (GTK_GRID(grid), ok_button, POS_RIGHT, 2, 1, 1);
g_object_set (G_OBJECT(entry), "activates-default", TRUE, NULL);
g_object_set (G_OBJECT(ok_button), "can-default", TRUE, "has-default", TRUE, NULL);
g_signal_connect (G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
gtk_widget_show_all (window);
gtk_main();
return EXIT_SUCCESS;
}
void try_to_rename(void) {
char sanitized[MAX_NAME_LEN];
const gchar *new_name = gtk_entry_get_text (GTK_ENTRY(entry));
snprintf (sanitized, MAX_NAME_LEN, "%s", new_name);
sanitize_name (sanitized);
gtk_entry_set_text (GTK_ENTRY(entry), sanitized);
if (0 == strcmp (orig, sanitized)) {
display_warning ("Not funny", "Same name ?!");
} else if ('\0' == sanitized[0] ||
'\0' == orig[0]) {
display_warning ("U mad br0", "Empty huh ?!");
} else {
if (0 != access (orig, F_OK)) {
display_warning ("Snafu ?!", "The file doesnt exist anymore.");
} else {
rename (orig, sanitized);
}
gtk_main_quit();
}
}
void sanitize_name (char *str) {
char *ptr = str;
char allowed[] = "()_.-";
/* Discard all the leading ' space' */
while (*ptr) {
if (isspace((unsigned char) *ptr)) {
ptr++;
} else {
break;
}
}
for (; *ptr; ptr++) {
if (isspace((unsigned char) *ptr) &&
(isalnum((unsigned char) *(ptr + 1)) ||
NULL != strchr(allowed, *(ptr + 1)))) {
*str++ = '_';
} else if (isalnum((unsigned char) *ptr) || (NULL != strchr(allowed, *ptr)
&& (*ptr != *(ptr + 1)))) {
*str++ = *ptr;
}
}
*str = '\0';
}
void display_warning(const char *str1, const char *str2) {
GtkWidget *warning = gtk_message_dialog_new(
GTK_WINDOW(window), GTK_DIALOG_MODAL,
GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "%s", str1
);
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(warning), "%s", str2);
gtk_dialog_run (GTK_DIALOG(warning));
gtk_widget_destroy (warning);
} |
|
|
Back to top |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|