emacs: some updates

This commit is contained in:
Pavel Korytov 2025-11-23 21:52:49 +03:00
parent 6c489b4a32
commit 2e64d77ba8
5 changed files with 153 additions and 38 deletions

View file

@ -381,14 +381,14 @@ DIR is either 'left or 'right."
_t_: Terminal (Alacritty) _t_: Terminal (Alacritty)
_b_: Browser (Firefox) _b_: Browser (Firefox)
_s_: Rocket.Chat _s_: Rocket.Chat
_e_: Element _d_: DBeaver
_d_: Discord _c_: Chromium
" "
("t" (lambda () (interactive) (my/run-in-background "alacritty"))) ("t" (lambda () (interactive) (my/run-in-background "alacritty")))
("b" (lambda () (interactive) (my/run-in-background "firefox"))) ("b" (lambda () (interactive) (my/run-in-background "firefox")))
("s" (lambda () (interactive) (my/run-in-background "flatpak run chat.rocket.RocketChat"))) ("s" (lambda () (interactive) (my/run-in-background "rocketchat-desktop")))
("e" (lambda () (interactive) (my/run-in-background "flatpak run im.riot.Riot"))) ("d" (lambda () (interactive) (my/run-in-background "dbeaver")))
("d" (lambda () (interactive) (my/run-in-background "flatpak run com.discordapp.Discord")))) ("c" (lambda () (interactive) (my/run-in-background "chromium"))))
(defun my/exwm-lock () (defun my/exwm-lock ()
(interactive) (interactive)
@ -594,10 +594,10 @@ _d_: Discord
(my/exwm-set-wallpaper) (my/exwm-set-wallpaper)
(my/exwm-run-shepherd) (my/exwm-run-shepherd)
(my/exwm-run-systemd)
(my/run-in-background "gpgconf --reload gpg-agent") (my/run-in-background "gpgconf --reload gpg-agent")
(my/exwm-run-polybar) (my/exwm-run-polybar)
(setenv "DBUS_SESSION_BUS_ADDRESS" "unix:path=/run/user/1000/bus") (setenv "DBUS_SESSION_BUS_ADDRESS" "unix:path=/run/user/1000/bus")
(my/exwm-run-systemd)
(when (my/is-arch) (when (my/is-arch)
(my/run-in-background "set_layout"))) (my/run-in-background "set_layout")))

View file

@ -82,6 +82,8 @@
;; I have everything I need in polybar ;; I have everything I need in polybar
(emms-mode-line-mode -1) (emms-mode-line-mode -1)
(emms-playing-time-display-mode -1) (emms-playing-time-display-mode -1)
(delq 'emms-mark-mode evil-emacs-state-modes)
(delq 'emms-browser-mode evil-emacs-state-modes)
(defun emms-info-mpd-process (track info) (defun emms-info-mpd-process (track info)
(dolist (data info) (dolist (data info)
(let ((name (car data)) (let ((name (car data))

View file

@ -375,8 +375,11 @@ REMOTE = '<rclone-remote>'
FOLDERS = json.loads('<rclone-folders-json>') FOLDERS = json.loads('<rclone-folders-json>')
OPTIONS = json.loads('<rclone-options>') OPTIONS = json.loads('<rclone-options>')
for folder, i in zip(FOLDERS, range(len(FOLDERS))):
folder['id'] = i
def rclone_make_command(local_path, remote_path, remote):
def rclone_make_command(local_path, remote_path, remote, extra_args=[]):
return [ return [
'rclone', 'rclone',
'bisync', 'bisync',
@ -390,7 +393,8 @@ def rclone_make_command(local_path, remote_path, remote):
'NEVER', 'NEVER',
'--use-json-log', '--use-json-log',
'--stats', '--stats',
'9999m' '9999m',
*extra_args
] ]
@ -418,15 +422,26 @@ def process_output(output):
except Exception: except Exception:
print(line) print(line)
def rclone_run(folder): def process_command_for_print(command):
res = []
for c in command:
if ' ' in c:
res.append(f'\\'{c}\\'')
else:
res.append(c)
return ' '.join(res)
def rclone_run(folder, extra_args=[]):
command = rclone_make_command( command = rclone_make_command(
folder['local-path'], folder['remote-path'], folder['remote'] folder['local-path'], folder['remote-path'], folder['remote'], extra_args
) )
try: try:
print(str(folder['id']) + '. ' + process_command_for_print(command))
result = subprocess.run(command, check=True, capture_output=True, text=True) result = subprocess.run(command, check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f'=== Error syncing {folder['local-path']} ===') print(f'=== Error syncing {folder['local-path']} ===')
print(f'Command: {' '.join(command)}') print(f'Command: {process_command_for_print(command)}')
print(f'--- STDOUT ---') print(f'--- STDOUT ---')
process_output(e.stdout) process_output(e.stdout)
print(f'--- STDERR ---') print(f'--- STDERR ---')
@ -434,7 +449,6 @@ def rclone_run(folder):
return {'success': False, 'stats': {}} return {'success': False, 'stats': {}}
return {'success': True, 'stats': parse_rclone_stats(result.stderr)} return {'success': True, 'stats': parse_rclone_stats(result.stderr)}
def notify(summary, body, level='normal', expire_time=5000): def notify(summary, body, level='normal', expire_time=5000):
subprocess.run(['notify-send', '-u', level, '-t', str(expire_time), summary, body]) subprocess.run(['notify-send', '-u', level, '-t', str(expire_time), summary, body])
@ -447,17 +461,16 @@ def sizeof_fmt(num, suffix='B'):
return f'{num:.1f}Yi{suffix}' return f'{num:.1f}Yi{suffix}'
def rclone_run_all(folders): def rclone_run_all(folders, extra_args=[]):
error_folders = [] error_folders = []
total_bytes = 0 total_bytes = 0
total_transfers = 0 total_transfers = 0
total_deleted = 0 total_deleted = 0
total_renamed = 0 total_renamed = 0
for folder in folders: for folder in folders:
print(f'Running rclone for {folder}') res = rclone_run(folder, extra_args)
res = rclone_run(folder)
if not res['success']: if not res['success']:
error_folders.append(folder['local-path']) error_folders.append(folder)
else: else:
total_bytes += res.get('stats', {}).get('bytes', 0) total_bytes += res.get('stats', {}).get('bytes', 0)
total_transfers += res.get('stats', {}).get('transfers', 0) total_transfers += res.get('stats', {}).get('transfers', 0)
@ -469,21 +482,44 @@ def rclone_run_all(folders):
if total_transfers > 0: if total_transfers > 0:
msg += f'''Transferred {total_transfers} files ({sizeof_fmt(total_bytes)})\n''' msg += f'''Transferred {total_transfers} files ({sizeof_fmt(total_bytes)})\n'''
if total_deleted > 0: if total_deleted > 0:
msg += f'''Deleted {total_transfers} files\n''' msg += f'''Deleted {total_deleted} files\n'''
if total_renamed > 0: if total_renamed > 0:
msg += f'''Renamed {total_renamed} files\n''' msg += f'''Renamed {total_renamed} files\n'''
if len(error_folders) > 0: if len(error_folders) > 0:
msg += '''\nSync errors for the following folders:''' msg += '''\nSync errors for the following folders:'''
for folder in error_folders: for folder in error_folders:
msg += '''\n- ''' + folder msg += '''\n- ''' + str(folder['id']) + '. ' + folder['local-path']
level = 'critical' level = 'critical'
if len(msg) > 0: if len(msg) > 0:
notify(f'rclone sync {REMOTE}', msg, level=level) notify(f'rclone sync {REMOTE}', msg, level=level)
def parse_arguments():
if len(sys.argv) < 2:
return None, []
id_arg = sys.argv[1]
folder_ids = [int(x.strip()) for x in id_arg.split(',')]
extra_args = sys.argv[2:]
return folder_ids, extra_args
if __name__ == '__main__': if __name__ == '__main__':
rclone_run_all(FOLDERS) folder_ids, extra_args = parse_arguments()
if folder_ids is None:
selected_folders = FOLDERS
else:
selected_folders = [f for f in FOLDERS if f['id'] in folder_ids]
found_ids = {f['id'] for f in selected_folders}
missing_ids = set(folder_ids) - found_ids
if missing_ids:
print(f'Warning: folder IDs not found: {sorted(missing_ids)}')
rclone_run_all(selected_folders, extra_args)
")) "))
(setq script (setq script
(thread-last script (thread-last script
@ -562,6 +598,23 @@ The return value is a list of commands as defined by
commands)) commands))
(nreverse commands))) (nreverse commands)))
(defun my/index-rclone-reset ()
"Delete all rclone test files."
(interactive)
(let* ((tree (my/index--tree-retrive))
(folders (my/index--rclone-get-folders tree))
files-to-delete)
(dolist (folder folders)
(let ((test-file-path
(concat
(alist-get :local-path folder)
(format ".rclone-test-%s" (alist-get :remote folder)))))
(when (file-exists-p test-file-path)
(push test-file-path files-to-delete))))
(when (y-or-n-p (format "Delete %d files" (seq-length files-to-delete)))
(dolist (file files-to-delete)
(delete-file file)))))
(defun my/index--git-commands (tree) (defun my/index--git-commands (tree)
"Get commands to clone the yet uncloned git repos in TREE. "Get commands to clone the yet uncloned git repos in TREE.

View file

@ -965,14 +965,14 @@ A +transient+ hydra for shortcuts for the most frequent apps.
_t_: Terminal (Alacritty) _t_: Terminal (Alacritty)
_b_: Browser (Firefox) _b_: Browser (Firefox)
_s_: Rocket.Chat _s_: Rocket.Chat
_e_: Element _d_: DBeaver
_d_: Discord _c_: Chromium
" "
("t" (lambda () (interactive) (my/run-in-background "alacritty"))) ("t" (lambda () (interactive) (my/run-in-background "alacritty")))
("b" (lambda () (interactive) (my/run-in-background "firefox"))) ("b" (lambda () (interactive) (my/run-in-background "firefox")))
("s" (lambda () (interactive) (my/run-in-background "flatpak run chat.rocket.RocketChat"))) ("s" (lambda () (interactive) (my/run-in-background "rocketchat-desktop")))
("e" (lambda () (interactive) (my/run-in-background "flatpak run im.riot.Riot"))) ("d" (lambda () (interactive) (my/run-in-background "dbeaver")))
("d" (lambda () (interactive) (my/run-in-background "flatpak run com.discordapp.Discord")))) ("c" (lambda () (interactive) (my/run-in-background "chromium"))))
#+end_src #+end_src
*** Locking up *** Locking up
Run i3lock. Run i3lock.
@ -1219,10 +1219,10 @@ And the EXWM config itself.
(my/exwm-set-wallpaper) (my/exwm-set-wallpaper)
(my/exwm-run-shepherd) (my/exwm-run-shepherd)
(my/exwm-run-systemd)
(my/run-in-background "gpgconf --reload gpg-agent") (my/run-in-background "gpgconf --reload gpg-agent")
(my/exwm-run-polybar) (my/exwm-run-polybar)
(setenv "DBUS_SESSION_BUS_ADDRESS" "unix:path=/run/user/1000/bus") (setenv "DBUS_SESSION_BUS_ADDRESS" "unix:path=/run/user/1000/bus")
(my/exwm-run-systemd)
(when (my/is-arch) (when (my/is-arch)
(my/run-in-background "set_layout"))) (my/run-in-background "set_layout")))
@ -4239,6 +4239,7 @@ This section generates manifests for various desktop software that I'm using.
| office | okular | | office | okular |
| office | obs-studio | | office | obs-studio |
| office | rocketchat-desktop | | office | rocketchat-desktop |
** LaTeX ** LaTeX
| Category | Arch dependency | Disabled | | Category | Arch dependency | Disabled |
|----------+--------------------------+----------| |----------+--------------------------+----------|
@ -4540,6 +4541,7 @@ Other desktop programs I use are listed below.
| desktop-misc | noto-fonts-emoji | | | desktop-misc | noto-fonts-emoji | |
| desktop-misc | remmina | | | desktop-misc | remmina | |
| desktop-misc | android-file-transfer | | | desktop-misc | android-file-transfer | |
| desktop-misc | veracrypt | |
#+NAME: packages #+NAME: packages
#+begin_src emacs-lisp :tangle no #+begin_src emacs-lisp :tangle no

View file

@ -10433,6 +10433,8 @@ References:
;; I have everything I need in polybar ;; I have everything I need in polybar
(emms-mode-line-mode -1) (emms-mode-line-mode -1)
(emms-playing-time-display-mode -1) (emms-playing-time-display-mode -1)
(delq 'emms-mark-mode evil-emacs-state-modes)
(delq 'emms-browser-mode evil-emacs-state-modes)
<<emms-fixes>>) <<emms-fixes>>)
#+end_src #+end_src
**** MPD **** MPD
@ -12870,8 +12872,11 @@ REMOTE = '<rclone-remote>'
FOLDERS = json.loads('<rclone-folders-json>') FOLDERS = json.loads('<rclone-folders-json>')
OPTIONS = json.loads('<rclone-options>') OPTIONS = json.loads('<rclone-options>')
for folder, i in zip(FOLDERS, range(len(FOLDERS))):
folder['id'] = i
def rclone_make_command(local_path, remote_path, remote):
def rclone_make_command(local_path, remote_path, remote, extra_args=[]):
return [ return [
'rclone', 'rclone',
'bisync', 'bisync',
@ -12885,7 +12890,8 @@ def rclone_make_command(local_path, remote_path, remote):
'NEVER', 'NEVER',
'--use-json-log', '--use-json-log',
'--stats', '--stats',
'9999m' '9999m',
,*extra_args
] ]
@ -12913,15 +12919,26 @@ def process_output(output):
except Exception: except Exception:
print(line) print(line)
def rclone_run(folder): def process_command_for_print(command):
res = []
for c in command:
if ' ' in c:
res.append(f'\\'{c}\\'')
else:
res.append(c)
return ' '.join(res)
def rclone_run(folder, extra_args=[]):
command = rclone_make_command( command = rclone_make_command(
folder['local-path'], folder['remote-path'], folder['remote'] folder['local-path'], folder['remote-path'], folder['remote'], extra_args
) )
try: try:
print(str(folder['id']) + '. ' + process_command_for_print(command))
result = subprocess.run(command, check=True, capture_output=True, text=True) result = subprocess.run(command, check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f'=== Error syncing {folder['local-path']} ===') print(f'=== Error syncing {folder['local-path']} ===')
print(f'Command: {' '.join(command)}') print(f'Command: {process_command_for_print(command)}')
print(f'--- STDOUT ---') print(f'--- STDOUT ---')
process_output(e.stdout) process_output(e.stdout)
print(f'--- STDERR ---') print(f'--- STDERR ---')
@ -12929,7 +12946,6 @@ def rclone_run(folder):
return {'success': False, 'stats': {}} return {'success': False, 'stats': {}}
return {'success': True, 'stats': parse_rclone_stats(result.stderr)} return {'success': True, 'stats': parse_rclone_stats(result.stderr)}
def notify(summary, body, level='normal', expire_time=5000): def notify(summary, body, level='normal', expire_time=5000):
subprocess.run(['notify-send', '-u', level, '-t', str(expire_time), summary, body]) subprocess.run(['notify-send', '-u', level, '-t', str(expire_time), summary, body])
@ -12942,17 +12958,16 @@ def sizeof_fmt(num, suffix='B'):
return f'{num:.1f}Yi{suffix}' return f'{num:.1f}Yi{suffix}'
def rclone_run_all(folders): def rclone_run_all(folders, extra_args=[]):
error_folders = [] error_folders = []
total_bytes = 0 total_bytes = 0
total_transfers = 0 total_transfers = 0
total_deleted = 0 total_deleted = 0
total_renamed = 0 total_renamed = 0
for folder in folders: for folder in folders:
print(f'Running rclone for {folder}') res = rclone_run(folder, extra_args)
res = rclone_run(folder)
if not res['success']: if not res['success']:
error_folders.append(folder['local-path']) error_folders.append(folder)
else: else:
total_bytes += res.get('stats', {}).get('bytes', 0) total_bytes += res.get('stats', {}).get('bytes', 0)
total_transfers += res.get('stats', {}).get('transfers', 0) total_transfers += res.get('stats', {}).get('transfers', 0)
@ -12964,21 +12979,44 @@ def rclone_run_all(folders):
if total_transfers > 0: if total_transfers > 0:
msg += f'''Transferred {total_transfers} files ({sizeof_fmt(total_bytes)})\n''' msg += f'''Transferred {total_transfers} files ({sizeof_fmt(total_bytes)})\n'''
if total_deleted > 0: if total_deleted > 0:
msg += f'''Deleted {total_transfers} files\n''' msg += f'''Deleted {total_deleted} files\n'''
if total_renamed > 0: if total_renamed > 0:
msg += f'''Renamed {total_renamed} files\n''' msg += f'''Renamed {total_renamed} files\n'''
if len(error_folders) > 0: if len(error_folders) > 0:
msg += '''\nSync errors for the following folders:''' msg += '''\nSync errors for the following folders:'''
for folder in error_folders: for folder in error_folders:
msg += '''\n- ''' + folder msg += '''\n- ''' + str(folder['id']) + '. ' + folder['local-path']
level = 'critical' level = 'critical'
if len(msg) > 0: if len(msg) > 0:
notify(f'rclone sync {REMOTE}', msg, level=level) notify(f'rclone sync {REMOTE}', msg, level=level)
def parse_arguments():
if len(sys.argv) < 2:
return None, []
id_arg = sys.argv[1]
folder_ids = [int(x.strip()) for x in id_arg.split(',')]
extra_args = sys.argv[2:]
return folder_ids, extra_args
if __name__ == '__main__': if __name__ == '__main__':
rclone_run_all(FOLDERS) folder_ids, extra_args = parse_arguments()
if folder_ids is None:
selected_folders = FOLDERS
else:
selected_folders = [f for f in FOLDERS if f['id'] in folder_ids]
found_ids = {f['id'] for f in selected_folders}
missing_ids = set(folder_ids) - found_ids
if missing_ids:
print(f'Warning: folder IDs not found: {sorted(missing_ids)}')
rclone_run_all(selected_folders, extra_args)
#+end_src #+end_src
A function that templates the script above: A function that templates the script above:
@ -13073,6 +13111,26 @@ The return value is a list of commands as defined by
(nreverse commands))) (nreverse commands)))
#+end_src #+end_src
Also, a command to remove all =.rclone-test-*= files. This is necessary, e.g. after a change in the filter file.
#+begin_src emacs-lisp
(defun my/index-rclone-reset ()
"Delete all rclone test files."
(interactive)
(let* ((tree (my/index--tree-retrive))
(folders (my/index--rclone-get-folders tree))
files-to-delete)
(dolist (folder folders)
(let ((test-file-path
(concat
(alist-get :local-path folder)
(format ".rclone-test-%s" (alist-get :remote folder)))))
(when (file-exists-p test-file-path)
(push test-file-path files-to-delete))))
(when (y-or-n-p (format "Delete %d files" (seq-length files-to-delete)))
(dolist (file files-to-delete)
(delete-file file)))))
#+end_src
**** Git repos **** Git repos
To sync git, we just need to clone the required git repos. Removing the repos is handled by the folder sync commands. To sync git, we just need to clone the required git repos. Removing the repos is handled by the folder sync commands.