commit 1416ffeaf40335e3cbe7549105234a7b607baee7 Author: Sivaldo <gxixtx@xsxixvxaxlxdxoxdxaxvxix.xcxoxm> Date: Sat, 27 Jun 2026 15:00:59 -0300 Commit inicial Diffstat:
| A | .config/X11/profile | | | 72 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .config/X11/xinitrc | | | 43 | +++++++++++++++++++++++++++++++++++++++++++ |
| A | .config/aerc/aerc.conf | | | 840 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .config/aerc/binds.conf | | | 240 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .config/git/config | | | 12 | ++++++++++++ |
| A | .config/lf/icons | | | 76 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .config/lf/lfrc | | | 75 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .config/nsxiv/exec/key-handler | | | 10 | ++++++++++ |
| A | .config/shell/ashrc | | | 90 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .config/vim/vimrc | | | 108 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .gitignore | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/backup | | | 94 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/bt | | | 88 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/clip | | | 39 | +++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/culto | | | 439 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/draw | | | 302 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/epub | | | 42 | ++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/install | | | 401 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/kindle | | | 138 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/monitor | | | 42 | ++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/open | | | 42 | ++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/pass-menu | | | 10 | ++++++++++ |
| A | .local/bin/ptman | | | 214 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/recorder | | | 71 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/share | | | 102 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/statusbar | | | 91 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/sys | | | 9 | +++++++++ |
| A | .local/bin/trans | | | 177 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | .local/bin/yt | | | 54 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | README.md | | | 30 | ++++++++++++++++++++++++++++++ |
30 files changed, 3996 insertions(+), 0 deletions(-)
diff --git a/.config/X11/profile b/.config/X11/profile @@ -0,0 +1,72 @@ + +# --------------------------- +# Programas +# --------------------------- +export EDITOR="vim" +export TERMINAL="st" +export BROWSER="firefox" +export READER="zathura" +export PATH="$HOME/.local/bin:$PATH" +export PASSWORD_STORE_GPG_OPTS="--trust-model always" + +# --------------------------- +# Variáveis de base (XDG) +# --------------------------- +export XDG_STATE_HOME="$HOME/.local/state" +export XDG_DATA_HOME="$HOME/.local/share" +export XDG_CACHE_HOME="$HOME/.cache" +export XDG_CONFIG_HOME="$HOME/.config" +export XDG_DOWNLOAD_DIR="$HOME/Downloads" +export XDG_DOCUMENTS_DIR="$HOME/documents" +export XDG_MUSIC_DIR="$HOME/music" +export XDG_PICTURES_DIR="$HOME/pictures" +export XDG_VIDEOS_DIR="$HOME/videos" + +# --------------------------- +# Pastas de programas +# --------------------------- +export PASSWORD_STORE_DIR="$XDG_DATA_HOME/password-store" +export GNUPGHOME="$XDG_DATA_HOME/gnupg" +export GOPATH="$XDG_DATA_HOME/go" +export HISTFILE="$XDG_CACHE_HOME/ash_history" +export ENV="$XDG_CONFIG_HOME/shell/ashrc" +export MYVIMRC="$XDG_CONFIG_HOME/vim/vimrc" +export SSL_DIR="$XDG_DATA_HOME/pki" +export NSS_DEFAULT_DB_TYPE="sql" +export GIT_CONFIG_GLOBAL="$XDG_CONFIG_HOME/git/config" +export DIR_NOTES="$HOME/notes" +export DIR_DEV="$HOME/.local/dev" + +# --------------------------- +# W3M +# --------------------------- +export W3M_DIR="$XDG_STATE_HOME/w3m" +export W3M_CONFIG="$XDG_CONFIG_HOME/w3m/config" +export W3M_TMPFILE="$XDG_CACHE_HOME/w3m" +export W3M_HOME="$XDG_DATA_HOME/w3m" + +# --------------------------- +# RUNTIME +# --------------------------- +export XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/tmp/$(id -u)-runtime-dir} +[ ! -d "$XDG_RUNTIME_DIR" ] && mkdir -m 0700 -p "$XDG_RUNTIME_DIR" + +# --------------------------- +# Outros Exports +# --------------------------- +export XAUTHORITY="$XDG_CONFIG_HOME/X11/xauthority" +export XINITRC="$XDG_CONFIG_HOME/x11/xinitrc" + +# Rust +export CARGO_HOME="$HOME/.local/share/cargo" +export PATH="$CARGO_HOME/bin:$PATH" + +# --------------------------- +# Automatically start dwm in tty +# --------------------------- +[ "$(tty)" = "/dev/tty1" ] && ! pidof -s Xorg >/dev/null 2>&1 && exec startx + +# --------------------------- +# Carregar o arquivo de shell +# --------------------------- +[ -f "$XDG_CONFIG_HOME/shell/ashrc" ] && . "$XDG_CONFIG_HOME/shell/ashrc" diff --git a/.config/X11/xinitrc b/.config/X11/xinitrc @@ -0,0 +1,43 @@ +#/bin/sh + +export $(dbus-launch) + +# --------------------------- +# Idioma +# --------------------------- +export LANG=pt_BR.UTF-8 + +# --------------------------- +# Barra de status +# --------------------------- +statusbar & + +# --------------------------- +# Som +# --------------------------- +/usr/libexec/pipewire-launcher & + +# --------------------------- +# Teclado +# --------------------------- +setxkbmap -model thinkpad -layout br -variant abnt2 + +# --------------------------- +# Esconder mouse +# --------------------------- +unclutter-xfixes -idle 3 -root & + +# --------------------------- +# Bloqueio de tela +# --------------------------- +xautolock -locker slock -time 30 & + +# --------------------------- +# Clip-daemon +# --------------------------- +clip daemon & + +# --------------------------- +# Gerenciador de janela +# --------------------------- +exec dwm diff --git a/.config/aerc/aerc.conf b/.config/aerc/aerc.conf @@ -0,0 +1,840 @@ +# Configuração principal do aerc + +[general] +# +# Usado como caminho padrão para operações de salvamento quando nenhum outro +# caminho é especificado. O ~ é expandido para o diretório home do usuário atual. +# +default-save-path= ~/documents + +# Se definido como "gpg", o aerc usará o binário gpg do sistema e o repositório +# de chaves para todas as operações criptográficas. Se "internal", o repositório +# interno openpgp será usado. Se "auto", o gpg do sistema é preferido, a menos +# que o repositório interno já exista, caso em que este último será usado. +# +# Padrão: auto +#pgp-provider=auto + +# Por padrão, as permissões do arquivo accounts.conf devem ser restritivas e +# permitir leitura apenas pelo dono do arquivo (0600). Defina como true para +# ignorar essa verificação. Use com cuidado, pois pode expor suas credenciais. +# +# Padrão: false +#unsafe-accounts-conf=false + +# Registra mensagens de log no arquivo especificado. Um caminho iniciado com ~/ +# é expandido para o home do usuário. Quando a saída do aerc é redirecionada +# para um arquivo com > no shell, essa configuração é ignorada e as mensagens +# são impressas no stdout. +# +#log-file= + +# Registra apenas mensagens acima do nível especificado no log-file. Os níveis +# suportados são: trace, debug, info, warn e error. Quando redirecionado via +# shell, essa configuração é ignorada e o nível é forçado para trace. +# +# Padrão: info +#log-level=info + +# Desativa o IPC completamente. Não executa comandos (incluindo mailto:... e +# mbox:...) em uma instância existente do aerc, e não inicia um servidor IPC +# para permitir que instâncias subsequentes executem comandos na atual. +# +# Padrão: false +#disable-ipc=false + +# Não executa comandos mailto:... via IPC; inicia uma nova instância do aerc +# com o compositor. +# +# Padrão: false +#disable-ipc-mailto=false +# +# Não executa comandos mbox:... via IPC; inicia uma nova instância do aerc +# com o arquivo mbox. +# +# Padrão: false +#disable-ipc-mbox=false + +# Define a variável de ambiente $TERM usada para o terminal embutido. +# +# Padrão: xterm-256color +#term=xterm-256color + +# Exibe strings OSC8 no terminal embutido. +# +# Padrão: false +#enable-osc8=false + +# Comando shell padrão para usar com :menu. Será executado com sh -c e +# exibido em um diálogo popover. +# +# Qualquer ocorrência de %f será substituída por um caminho de arquivo +# temporário onde o comando deve escrever as linhas de saída a serem +# consumidas pelo :menu. Caso contrário, as linhas serão lidas da saída +# padrão do comando. +# +# Exemplos: +# default-menu-cmd=fzf +# default-menu-cmd=fzf --multi +# default-menu-cmd=dmenu -l 20 +# default-menu-cmd=ranger --choosefiles=%f +# +#default-menu-cmd= + +[ui] +# +# Descreve o formato de cada linha na visualização de caixa de entrada. É uma +# lista separada por vírgulas de nomes de colunas com sufixo opcional de +# alinhamento e largura. Após o nome da coluna, pode-se adicionar '<' (esquerda), +# ':' (centro) ou '>' (direita) seguido de um especificador de largura opcional. +# A largura pode ser um inteiro (caracteres fixos), uma porcentagem (1%–99%) ou +# '*' (automático) ou '=' (ajustado ao conteúdo). O padrão é '*'. +# +# Padrão: flags:4,name<20%,subject,date>= +#index-columns=flags:4,name<20%,subject,date>= + +# +# Cada nome em index-columns deve ter uma configuração column-$name correspondente. +# Todas aceitam sintaxe golang text/template. Veja aerc-templates(7) para atributos +# e funções disponíveis. +# +# Exemplos para exibir o campo To em vez de From: +# +# 1. genérico +# column-name={{ .Peer | names | join ", " }} +# 2. baseado na pasta selecionada +# column-name={{if match .Folder "^(Gesendet|Sent)$"}}{{index (.To | names) 0}}{{else}}{{index (.From | names) 0}}{{end}} +# +# Configurações padrão +#column-flags={{.Flags | join ""}} +#column-name={{index (.From | names) 0}} +#column-subject={{.ThreadPrefix}}{{.Subject}} +#column-date={{.DateAutoFormat .Date.Local}} + +# +# Separador inserido entre colunas. Quando o especificador de largura for um +# número exato de caracteres, o separador é somado a ele. +# +# Padrão: " " +#column-separator=" " + +# +# Veja time.Time#Format em https://godoc.org/time#Time.Format +# +# Padrão: 2006 Jan 02 +#timestamp-format=2006 Jan 02 + +# +# Formato de hora (somente no índice) para mensagens recebidas/enviadas hoje. +# Se vazio, usa timestamp-format. +# +# Padrão: 15:04 +#this-day-time-format=15:04 + +# +# Formato de hora (somente no índice) para mensagens recebidas/enviadas nos +# últimos 7 dias. Se vazio, usa timestamp-format. +# +# Padrão: Jan 02 +#this-week-time-format=Jan 02 + +# +# Formato de hora (somente no índice) para mensagens recebidas/enviadas este ano. +# Se vazio, usa timestamp-format. +# +# Padrão: Jan 02 +#this-year-time-format=Jan 02 + +# +# Substitui timestamp-format na visualização de mensagem. +# +# Padrão: 2006 Jan 02, 15:04 GMT-0700 +#message-view-timestamp-format=2006 Jan 02, 15:04 GMT-0700 + +# +# Se definido, substitui timestamp-format na visualização de mensagens +# recebidas/enviadas hoje. +# +#message-view-this-day-time-format= + +# Se definido, substitui timestamp-format na visualização de mensagens +# recebidas/enviadas nos últimos 7 dias. +# +#message-view-this-week-time-format= + +# +# Se definido, substitui timestamp-format na visualização de mensagens +# recebidas/enviadas este ano. +# +#message-view-this-year-time-format= + +# +# Largura da barra lateral, incluindo a borda. +# +# Padrão: 22 +#sidebar-width=22 + +# +# Layout de divisão padrão para abas de lista de mensagens. A sintaxe é: +# +# [<direção>] <tamanho> +# +# <direção> é opcional e assume horizontal por padrão. Valores aceitos: +# h, horiz, horizontal, v, vert, vertical. +# +# <tamanho> é um inteiro positivo representando o tamanho (em células do +# terminal) da janela de lista de mensagens. +# +#message-list-split= + +# +# Mensagem exibida ao visualizar uma pasta vazia. +# +# Padrão: (no messages) +#empty-message=(no messages) + +# Mensagem exibida quando não existem pastas ou todas estão filtradas. +# +# Padrão: (no folders) +#empty-dirlist=(no folders) +# +# Valor atribuído ao template {{.Subject}} quando o assunto está vazio. +# +# Padrão: (no subject) +#empty-subject=(no subject) + +# Ativa eventos de mouse na interface, como clique e scroll com a rodinha. +# +# Padrão: false +#mouse-enabled=false + +# +# Toca um sino quando novas mensagens são recebidas. +# +# Padrão: true +#new-message-bell=true + +# +# Template para títulos das abas de Conta. +# +# Padrão: {{.Account}} +#tab-title-account={{.Account}} + +# +# Template para títulos das abas do Compositor. +# +# Padrão: {{if .To}}to:{{index (.To | shortmboxes) 0}} {{end}}{{.SubjectBase}} +#tab-title-composer={{if .To}}to:{{index (.To | shortmboxes) 0}} {{end}}{{.SubjectBase}} + +# +# Template para títulos das abas de Terminal. Válido apenas na seção global [ui]. +# +# Padrão: {{.Title}} +#tab-title-terminal={{.Title}} + +# +# Template para títulos das abas do Visualizador de mensagens. +# +# Padrão: {{.Subject}} +#tab-title-viewer={{.Subject}} + +# Marcador exibido antes do nome de uma aba fixada. +# +# Padrão: ` +#pinned-tab-marker='`' + +# Template para o lado esquerdo da lista de diretórios. +# Veja aerc-templates(7) para todos os campos e funções disponíveis. +# +# Padrão: {{.Folder}} +#dirlist-left={{.Folder}} + +# Template para o lado direito da lista de diretórios. +# Veja aerc-templates(7) para todos os campos e funções disponíveis. +# +# Padrão: {{if .Unread}}{{humanReadable .Unread}}{{end}} +#dirlist-right={{if .Unread}}{{humanReadable .Unread}}{{end}} + +# Atraso após o qual as mensagens são realmente listadas ao entrar em um +# diretório. Evita carregar mensagens ao navegar rapidamente entre pastas, +# tornando a interface mais responsiva. Para desativar, defina como 0s. +# +# Padrão: 200ms +#dirlist-delay=200ms + +# Exibe a lista de diretórios como uma árvore recolhível que permite expandir +# e recolher as pastas. +# +# Padrão: false +#dirlist-tree=false + +# Se dirlist-tree estiver ativado, define o nível a partir do qual as pastas +# ficam recolhidas por padrão. Defina como 0 para desativar. +# +# Padrão: 0 +#dirlist-collapse=0 + +# Lista de critérios separados por espaço para ordenar as mensagens. Veja o +# comando *sort* em *aerc*(1) para referência. Prefixar um critério com "-r " +# inverte a ordenação. +# +# Exemplo: "from -r date" +# +#sort= + +# Move para a próxima mensagem quando a mensagem atual é excluída. +# +# Padrão: true +#next-message-on-delete=true + +# Define automaticamente o sinalizador "seen" ao abrir uma mensagem no +# visualizador. +# +# Padrão: true +#auto-mark-read=true + +# Especifica se o sinalizador _seen_ das mensagens sendo pré-visualizadas no +# painel dividido deve ser automaticamente definido como _true_ após um atraso +# controlado por _auto-mark-read-split-delay_. +# +# Padrão: false +#auto-mark-read-split=false + +# Se _auto-mark-read-split_ estiver ativo, define após quantos segundos uma +# mensagem pré-visualizada no painel dividido é considerada _lida_. +# +# Padrão: 3s +#auto-mark-read-split-delay=3s + +# Diretórios onde os conjuntos de estilos são armazenados. Aceita uma lista +# separada por dois-pontos. Se não definido ou se o styleset não for encontrado, +# os seguintes caminhos serão usados como fallback, nessa ordem: +# +# ${XDG_CONFIG_HOME:-~/.config}/aerc/stylesets +# ${XDG_DATA_HOME:-~/.local/share}/aerc/stylesets +# /usr/local/share/aerc/stylesets +# /usr/share/aerc/stylesets +# +#stylesets-dirs= + +# Descomente para usar caracteres de desenho de caixa nas bordas verticais e +# horizontais. +# +# Padrão: "│" e "─" +#border-char-vertical="│" +#border-char-horizontal="─" + +# Define o styleset a ser usado para os elementos da interface do aerc. +# +# Padrão: default +#styleset-name=default + +# Ativa a busca fuzzy em comandos e seus argumentos: o texto digitado é +# procurado no comando ou opção em qualquer posição, sem precisar ser +# caracteres consecutivos. +# +# Padrão: false +#fuzzy-complete=false + +# Tempo de espera após a última entrada antes de acionar o autocompletar. +# +# Padrão: 250ms +#completion-delay=250ms + +# Número mínimo de caracteres para permitir que o autocompletar seja acionado +# após completion-delay. +# +# Definir como "manual" desativa o autocompletar automático, deixando apenas +# o acionamento manual com a tecla $complete (veja aerc-binds(5)). +# +# Padrão: 1 +#completion-min-chars=1 + +# +# Chave global para popovers de autocompletar. +# +# Padrão: true +#completion-popovers=true + +# Descomente para usar símbolos UTF-8 para indicar o status PGP das mensagens. +# +# Padrão: ASCII +#icon-unencrypted= +#icon-encrypted=✔ +#icon-signed=✔ +#icon-signed-encrypted=✔ +#icon-unknown=✘ +#icon-invalid=⚠ + +# Inverte a ordem da lista de mensagens. Por padrão, a mensagem mais recente +# (maior UID) fica no topo. Invertendo, a mais antiga (menor UID) vai para o +# topo. Útil quando o backend não suporta ordenação. +# +# Padrão: false +#reverse-msglist-order = false + +# Inverte a exibição das threads. Por padrão, a mensagem inicial fica no topo +# com as respostas abaixo. Invertendo, a mensagem inicial vai para o fundo e +# as respostas ficam acima. +# +# Padrão: false +#reverse-thread-order=false + +# Posiciona o cursor na última mensagem da lista (no fundo da visualização) +# ao abrir uma nova pasta. +# +# Padrão: false +#select-last-message=false + +# Ordena os irmãos de thread de acordo com os critérios de ordenação das +# mensagens. Se false, os irmãos são ordenados pelo UID em ordem crescente. +# Se false e threading-by-subject for true, os irmãos são ordenados pelos +# cabeçalhos de assunto usando ordenação UTF-8. +# +# Aplicável apenas para threading do lado do cliente com backend que suporte +# ordenação. Há impacto de desempenho quando ativo. +# +# Padrão: false +#sort-thread-siblings=false + +# Define o deslocamento de rolagem em número de linhas a partir do topo e +# do fundo da lista de mensagens. +# +# Padrão: 0 +#msglist-scroll-offset = 0 + +# +# Ativa a visualização em threads. Se não for suportado pelo backend (servidor +# IMAP ou notmuch), as threads serão montadas pelo cliente. +# +# Padrão: false +#threading-enabled=false + +# Força a montagem de threads no lado do cliente. +# +# Padrão: false +#force-client-threads=false + +# Se nenhum cabeçalho References ou In-Reply-To puder ser combinado para montar +# threads no lado do cliente, recorre a assuntos similares. Também afeta a +# ordenação dos irmãos de thread quando sort-thread-siblings é false. +# +# Padrão: false +#threading-by-subject=false + +# Exibe mensagens que não correspondem à consulta atual (ou não pertencem à +# caixa de entrada atual) para dar contexto às threads. Essas mensagens podem +# ser estilizadas separadamente com "msglist_thread_context" em um styleset. +# Nem todos os backends suportam este recurso. +# +# Padrão: false +#show-thread-context=false + +# Debounce para a montagem de threads no lado do cliente. +# +# Padrão: 50ms +#client-threads-delay=50ms + +# +# Personalização do prefixo de thread: + +# +# Seleciona a ponta da seta do prefixo de thread. +# +# Padrão: ">" +#thread-prefix-tip = ">" + +# +# Seleciona o recuo da seta do prefixo de thread. +# +# Padrão: " " +#thread-prefix-indent = " " + +# +# Seleciona a extensão vertical da seta do prefixo de thread. +# +# Padrão: "│" +#thread-prefix-stem = "│" + +# +# Seleciona a extensão horizontal da seta do prefixo de thread. +# +# Padrão: "" +#thread-prefix-limb = "" + +# +# Seleciona o indicador de thread recolhida. +# +# Padrão: "+" +#thread-prefix-folded = "+" + +# +# Seleciona o indicador de thread expandida. +# +# Padrão: "" +#thread-prefix-unfolded = "" + +# +# Seleciona o conector do primeiro filho no prefixo de thread. +# +# Padrão: "" +#thread-prefix-first-child = "" + +# +# Seleciona o conector usado quando a mensagem tem irmãos. +# +# Padrão: "├─" +#thread-prefix-has-siblings = "├─" + +# +# Seleciona o conector para mensagem sem pais e sem filhos. +# +# Padrão: "" +#thread-prefix-lone = "" + +# +# Seleciona o conector para mensagem sem pais mas com filhos. +# +# Padrão: "" +#thread-prefix-orphan = "" + +# +# Seleciona o conector para o último irmão. +# +# Padrão: "└─" +#thread-prefix-last-sibling = "└─" + +# +# Seleciona o conector para o último irmão no modo invertido. +# +# Padrão: "┌─" +#thread-prefix-last-sibling-reverse = "┌─" + +# +# Seleciona o conector para thread fantasma (dummy). +# +# Padrão: "┬─" +#thread-prefix-dummy = "┬─" + +# +# Seleciona o conector para thread fantasma no modo invertido. +# +# Padrão: "┴─" +#thread-prefix-dummy-reverse = "┴─" + +# +# Seleciona o conector do primeiro filho no modo invertido. +# +# Padrão: "" +#thread-prefix-first-child-reverse = "" + +# +# Seleciona o conector para mensagem sem pais e com filhos no modo invertido. +# +# Padrão: "" +#thread-prefix-orphan-reverse = "" + +[statusline] +# +# Descreve o formato da linha de status. É uma lista separada por vírgulas de +# nomes de colunas com sufixo opcional de alinhamento e largura. Veja +# [ui].index-columns para mais detalhes. Para silenciar completamente a linha +# de status (exceto notificações push), defina status-columns como string vazia. +# +# Padrão: left<*,center:=,right>* +#status-columns=left<*,center:=,right>* + +# +# Cada nome em status-columns deve ter uma configuração column-$name correspondente. +# Todas aceitam sintaxe golang text/template. Veja aerc-templates(7). +# +# Configurações padrão +#column-left=[{{.Account}}] {{.StatusInfo}} +#column-center={{.PendingKeys}} +#column-right={{.TrayInfo}} | {{cwd}} + +# +# Separador entre colunas. Veja [ui].column-separator para mais detalhes. +# +#column-separator=" " + +# Especifica o separador entre elementos agrupados na linha de status. +# +# Padrão: " | " +#separator=" | " + +# Define o modo de exibição dos elementos de status. +# Opções: text, icon +# +# Padrão: text +#display-mode=text + +[viewer] +# +# Especifica o pager a ser usado ao exibir e-mails. Alguns filtros podem +# adicionar códigos ANSI para colorir os e-mails renderizados, então convém +# usar um pager que suporte códigos ANSI. +# +# Padrão: less -Rc +#pager=less -Rc + +# +# Se um e-mail oferece várias versões (multipart), você pode configurar qual +# mimetype preferir. Por exemplo, para preferir texto simples em vez de HTML. +# +# Padrão: text/plain,text/html +#alternatives=text/plain,text/html + +# +# Define se os cabeçalhos completos ou apenas os interpretados serão exibidos +# no visualizador de mensagens. +# +# Padrão: false +#show-headers=false + +# +# Layout dos cabeçalhos ao visualizar uma mensagem. Para exibir múltiplos +# cabeçalhos na mesma linha, separe-os com pipe, ex.: "From|To". Linhas +# são ocultadas se nenhum dos cabeçalhos especificados estiver presente. +# +# Padrão: From|To,Cc|Bcc,Date,Subject +#header-layout=From|To,Cc|Bcc,Date,Subject + +# Se deve sempre mostrar o mimetype de um e-mail, mesmo quando for apenas +# uma parte única. +# +# Padrão: false +#always-show-mime=false + +# Define a altura máxima do seletor de mimetype antes de usar uma barra de +# rolagem. A altura é limitada a metade da altura da tela. Se o valor for +# zero, o número de partes será usado como altura. +# +# Padrão: 0 +#max-mime-height = 0 + +# Analisa e extrai links HTTP ao visualizar uma mensagem. Os links podem +# então ser acessados com o comando open-link. +# +# Padrão: true +#parse-http-links=true + +[compose] +# +# Especifica o comando para executar o editor. Será exibido em um terminal +# embutido, mas também pode abrir uma janela gráfica se o ambiente suportar. +# Usa $EDITOR por padrão, ou vi. +#editor= + +# +# Quando definido, o aerc criará e lerá arquivos .eml com quebras de linha +# não-padrão \n. Relevante apenas se o editor usado não suportar quebras CRLF. +# +#lf-editor=false + +# +# Campos de cabeçalho padrão exibidos ao compor uma mensagem. Para exibir +# múltiplos cabeçalhos na mesma linha, separe-os com pipe, ex.: "To|From". +# +# Padrão: To|From,Subject +#header-layout=To|From,Subject + +# +# Edita os cabeçalhos diretamente no editor de texto em vez de campos separados. +# +# Quando true, address-book-cmd não é suportado e o autocompletar de endereços +# fica a cargo do editor. Também não é possível exibir múltiplos cabeçalhos +# na mesma linha. +# +# Padrão: false +#edit-headers=false + +# +# Move o foco para o corpo do e-mail ao abrir a janela do compositor. +# +# Padrão: false +#focus-body=false + +# +# Especifica o comando para autocompletar endereços de e-mail. Toda ocorrência +# de "%s" será substituída pelo que o usuário digitou até o momento. +# +# O comando deve escrever os resultados na saída padrão, um por linha, separados +# por tabulação. O primeiro campo deve ser o endereço de e-mail (obrigatório). +# O segundo campo, se presente, será tratado como nome do contato. Os demais +# campos são ignorados. +# +# Este parâmetro também pode ser definido por conta em accounts.conf. +#address-book-cmd= + +# Especifica o comando para selecionar anexos. Toda ocorrência de '%s' será +# substituída pelo argumento <arg> de :attach -m <arg>. Toda ocorrência de '%f' +# será substituída pelo caminho de um arquivo temporário do qual o aerc lerá +# os arquivos selecionados. +# +# Se '%f' não estiver presente, o comando deve escrever os arquivos na saída +# padrão, um por linha. Se estiver presente, o aerc não captura a saída padrão +# e lê os arquivos do arquivo temporário, no mesmo formato. +#file-picker-cmd= + +# +# Permite endereçar a si mesmo ao responder. +# +# Padrão: true +#reply-to-self=true + +# Avisa antes de enviar um e-mail com assunto vazio. +# +# Padrão: false +#empty-subject-warning=false + +# +# Avisa antes de enviar um e-mail que corresponde à expressão regular +# especificada mas não tem anexos. Deixe vazio para desativar. +# +# Usa a sintaxe de regexp do Go, documentada em https://golang.org/s/re2syntax. +# Os flags "(?im)" são definidos por padrão (sem distinção de maiúsculas e +# multilinha). +# +# Exemplo: +# no-attachment-warning=^[^>]*attach(ed|ment) +# +#no-attachment-warning= + +# +# Quando definido, o aerc gerará corpos "format=flowed" com tipo de conteúdo +# "text/plain; format=flowed" conforme descrito na RFC3676. Esse formato é +# mais fácil de processar por alguns programas de e-mail, e geralmente parece +# texto comum. Para usar os recursos desse formato, seu editor precisa suportá-lo. +# +#format-flowed=false + +[multipart-converters] +# +# Conversores permitem gerar mensagens multipart/alternative convertendo a +# parte principal text/plain em qualquer outro tipo MIME. Apenas tipos MIME +# exatos são aceitos. Os comandos são invocados com sh -c e devem produzir +# texto UTF-8 válido. +# +# Exemplo (requer que o corpo principal text/plain seja escrito em Markdown): +#text/html=pandoc -f markdown -t html --standalone + +[filters] +# +# Filtros permitem redirecionar o corpo de um e-mail por um comando shell para +# renderizá-lo de forma diferente, ex.: destacando-o com códigos ANSI. +# +# Os comandos são invocados com sh -c. As seguintes pastas são adicionadas ao +# $PATH do sistema para permitir referenciar filtros pelo nome: +# +# ${XDG_CONFIG_HOME:-~/.config}/aerc/filters +# ~/.local/libexec/aerc/filters +# ${XDG_DATA_HOME:-~/.local/share}/aerc/filters +# $PREFIX/libexec/aerc/filters +# $PREFIX/share/aerc/filters +# /usr/libexec/aerc/filters +# /usr/share/aerc/filters +# +# Para executar um programa no $PATH padrão com o mesmo nome de um filtro +# embutido (ex.: /usr/bin/colorize), use seu caminho absoluto. +# +# As seguintes variáveis estão disponíveis no ambiente do comando de filtro: +# +# AERC_MIME_TYPE o tipo/subtipo MIME da parte +# AERC_FORMAT o parâmetro format= do tipo de conteúdo da parte +# AERC_FILENAME o nome do arquivo anexo (se houver) +# AERC_SUBJECT o valor do cabeçalho Subject da mensagem +# AERC_FROM o valor do cabeçalho From da mensagem +# +# O primeiro filtro que corresponder ao mimetype do e-mail será usado, portanto +# ordene-os do mais específico para o menos específico. +# +# Você também pode corresponder a não-mimetypes, prefixando com o cabeçalho a +# verificar (sem distinção de maiúsculas) e uma vírgula, ex.: subject,text +# corresponde a um assunto que contém "text". Use header,~regex para +# correspondência com expressão regular. +# +text/plain=colorize +text/calendar=calendar +message/delivery-status=colorize +message/rfc822=colorize +#text/html=pandoc -f html -t plain | colorize +text/html=! html +#text/html=! w3m -T text/html -I UTF-8 +#text/*=bat -fP --file-name="$AERC_FILENAME" +#application/x-sh=bat -fP -l sh +#image/*=catimg -w $(tput cols) - +#subject,~Git(hub|lab)=lolcat -f +#from,thatguywhodoesnothardwraphismessages=wrap -w 100 | colorize + +# Este filtro especial é usado apenas para pós-processar cabeçalhos de e-mail +# quando [viewer].show-headers=true. Por padrão, os cabeçalhos são enviados +# diretamente ao pager. +# +.headers=colorize + +[openers] +# O asterisco "*" diz ao aerc: "não importa o tipo, mande para o meu script" +* = open {} + +[hooks] +# +# Hooks são acionados sempre que o evento associado ocorre. + +# +# Executado quando um novo e-mail chega na pasta selecionada. +#mail-received=notify-send "[$AERC_ACCOUNT/$AERC_FOLDER] New mail from $AERC_FROM_NAME" "$AERC_SUBJECT" + +# +# Executado quando um e-mail é excluído de uma pasta. +#mail-deleted=mbsync "$AERC_ACCOUNT:$AERC_FOLDER" & + +# +# Executado quando o aerc adiciona um e-mail a uma pasta. +#mail-added=mbsync "$AERC_ACCOUNT:$AERC_FOLDER" & + +# +# Executado quando o aerc é iniciado. +#aerc-startup=aerc :terminal calcurse && aerc :next-tab + +# +# Executado quando o aerc é encerrado. +#aerc-shutdown= + +# +# Executado quando as tags do notmuch são modificadas. +#tag-modified= + +# +# Executado quando os sinalizadores de uma mensagem são alterados. +#flag-changed=mbsync "$AERC_ACCOUNT:$AERC_FOLDER" & + +[templates] +# Templates são usados para preencher automaticamente o corpo dos e-mails. +# + +# Diretórios onde os templates são armazenados. Aceita lista separada por +# dois-pontos. Se não definido ou se um template não for encontrado, os +# seguintes caminhos serão usados como fallback, nessa ordem: +# +# ${XDG_CONFIG_HOME:-~/.config}/aerc/templates +# ${XDG_DATA_HOME:-~/.local/share}/aerc/templates +# /usr/local/share/aerc/templates +# /usr/share/aerc/templates +# +#template-dirs= + +# Template padrão para novas mensagens. +# +# Padrão: new_message +#new-message=new_message + +# Template padrão para respostas com citação. +# +# Padrão: quoted_reply +#quoted-reply=quoted_reply + +# Template padrão para encaminhamento como corpo. +# +# Padrão: forward_as_body +#forwards=forward_as_body diff --git a/.config/aerc/binds.conf b/.config/aerc/binds.conf @@ -0,0 +1,240 @@ +# Os atalhos (binds) seguem o formato <tecla/sequência> = <comando a executar> +# Para usar '=' em uma sequência de teclas, substitua por "Eq": "<Ctrl+Eq>" +# Se você deseja vincular a tecla #, coloque a sequência entre aspas: "#" = quit + +# ============================================================================== +# ATALHOS GLOBAIS (Disponíveis em qualquer contexto, a menos que sobrescritos) +# ============================================================================== + +# --- Navegação por Abas --- +<C-p> = :prev-tab<Enter> # Ir para a aba anterior +<C-PgUp> = :prev-tab<Enter> # Ir para a aba anterior +<C-n> = :next-tab<Enter> # Ir para a próxima aba +<C-PgDn> = :next-tab<Enter> # Ir para a próxima aba +\[t = :prev-tab<Enter> # Ir para a aba anterior (alternativa) +\]t = :next-tab<Enter> # Ir para a próxima aba (alternativa) + +# --- Sistema e Terminal --- +<C-t> = :term<Enter> # Abrir um emulador de terminal numa nova aba +? = :help keys<Enter> # Mostrar a tela de ajuda com os atalhos de teclado +<C-c> = :prompt 'Quit?' quit<Enter> # Pedir confirmação para fechar o programa +<C-q> = :prompt 'Quit?' quit<Enter> # Pedir confirmação para fechar o programa +<C-z> = :suspend<Enter> # Suspender o aplicativo (enviar para o background/segundo plano) + +# ============================================================================== +# LISTA DE MENSAGENS +# ============================================================================== +[messages] + +q = :prompt 'Quit?' quit<Enter> # Pedir confirmação antes de sair + +# --- Movimentação Básica (Para Cima / Para Baixo) --- +j = :next<Enter> # Mensagem seguinte +<Down> = :next<Enter> # Mensagem seguinte +<C-d> = :next 50%<Enter> # Rolar a lista meia tela para baixo +<C-f> = :next 100%<Enter> # Rolar a lista uma tela inteira para baixo +<PgDn> = :next 100%<Enter> # Rolar a lista uma tela inteira para baixo + +k = :prev<Enter> # Mensagem anterior +<Up> = :prev<Enter> # Mensagem anterior +<C-u> = :prev 50%<Enter> # Rolar a lista meia tela para cima +<C-b> = :prev 100%<Enter> # Rolar a lista uma tela inteira para cima +<PgUp> = :prev 100%<Enter> # Rolar a lista uma tela inteira para cima +g = :select 0<Enter> # Pular para o topo (primeira mensagem) +G = :select -1<Enter> # Pular para o final (última mensagem) + +# --- Navegação de Pastas (Diretórios de Email) --- +J = :next-folder<Enter> # Ir para a próxima pasta +<C-Down> = :next-folder<Enter> # Ir para a próxima pasta +K = :prev-folder<Enter> # Ir para a pasta anterior +<C-Up> = :prev-folder<Enter> # Ir para a pasta anterior +H = :collapse-folder<Enter> # Recolher pasta atual (se for subpasta) +<C-Left> = :collapse-folder<Enter> # Recolher pasta atual +L = :expand-folder<Enter> # Expandir pasta atual +<C-Right> = :expand-folder<Enter> # Expandir pasta atual + +# --- Marcação de Mensagens (Seleção) --- +v = :mark -t<Enter> # Alternar seleção (marcar/desmarcar) da mensagem atual +<Space> = :mark -t<Enter>:next<Enter> # Marcar a mensagem e descer para a próxima +V = :mark -v<Enter> # Modo visual (marcar várias mensagens de uma vez) + +# --- Threads (Conversas em Árvore) --- +T = :toggle-threads<Enter> # Ativar/Desativar visualização em forma de conversas (threads) +zc = :fold<Enter> # Recolher/esconder a thread atual +zo = :unfold<Enter> # Expandir/mostrar a thread atual +za = :fold -t<Enter> # Alternar entre expandido/recolhido na thread atual +zM = :fold -a<Enter> # Recolher TODAS as threads +zR = :unfold -a<Enter> # Expandir TODAS as threads +<tab> = :fold -t<Enter> # Alternar expansão da thread (tecla tab) + +# --- Alinhamento Visual da Lista --- +zz = :align center<Enter> # Centralizar a mensagem selecionada no meio da tela +zt = :align top<Enter> # Colocar a mensagem selecionada no topo da tela +zb = :align bottom<Enter> # Colocar a mensagem selecionada no final da tela + +# --- Ações de Mensagem (Visualizar, Excluir, Arquivar) --- +<Enter> = :view<Enter> # Abrir a mensagem selecionada +d = :choose -o y 'Really delete this message' delete-message<Enter> # Excluir com confirmação +D = :delete<Enter> # Excluir imediatamente +a = :archive flat<Enter> # Arquivar mensagem +A = :unmark -a<Enter>:mark -T<Enter>:archive flat<Enter> # Arquivar toda a conversa (thread) + +# --- Escrever / Nova Mensagem --- +C = :compose<Enter> # Escrever novo email +m = :compose<Enter> # Escrever novo email (alternativa) + +# --- Redirecionar (Bounce) --- +b = :bounce<space> # Redirecionar mensagem intacta para outro endereço + +# --- Respostas --- +rr = :reply -a<Enter> # Responder a todos (Reply All) +rq = :reply -aq<Enter> # Responder a todos incluindo o texto original (Quote) +Rr = :reply<Enter> # Responder apenas ao remetente +Rq = :reply -q<Enter> # Responder ao remetente incluindo o texto original + +# --- Busca, Filtro e Comandos do Terminal --- +c = :cf<space> # Mudar de pasta (Change Folder) +$ = :term<space> # Abrir terminal executando um comando específico +! = :term<space> # (Idem) Abrir terminal +| = :pipe<space> # Enviar o conteúdo do email para um comando externo (Pipe) + +/ = :search<space> # Buscar emails +\ = :filter<space> # Filtrar emails na visualização atual +n = :next-result<Enter> # Pular para o próximo resultado da busca +N = :prev-result<Enter> # Pular para o resultado anterior da busca +<Esc> = :clear<Enter> # Limpar barra de busca/filtros + +# --- Gerenciamento de Janelas (Split) --- +s = :split<Enter> # Dividir tela horizontalmente +S = :vsplit<Enter> # Dividir tela verticalmente + +# --- Manipulação de Patches (Git/Desenvolvimento) --- +pl = :patch list<Enter> # Listar patches +pa = :patch apply <Tab> # Aplicar patch +pd = :patch drop <Tab> # Descartar patch +pb = :patch rebase<Enter> # Rebase usando patches +pt = :patch term<Enter> # Abrir terminal de patch +ps = :patch switch <Tab> # Trocar patch + +# ============================================================================== +# PASTA DE RASCUNHOS (Drafts) +# ============================================================================== +[messages:folder=Drafts] +<Enter> = :recall<Enter> # Ao dar Enter num rascunho, voltar a editá-lo + +# ============================================================================== +# TELA DE VISUALIZAÇÃO DA MENSAGEM LIDA +# ============================================================================== +[view] +/ = :toggle-key-passthrough<Enter>/ # Habilitar repasse de tecla para buscar dentro do paginador da mensagem +q = :close<Enter> # Fechar visualização e voltar para a lista +O = :open<Enter> # Abrir a mensagem no programa padrão do sistema (ex: navegador) +o = :open<Enter> # Idem +S = :save<space> # Salvar mensagem ou anexo +| = :pipe<space> # Enviar conteúdo da mensagem para comando do terminal +D = :delete<Enter> # Excluir a mensagem +A = :archive flat<Enter> # Arquivar a mensagem + +<C-y> = :copy-link <space> # Copiar link da mensagem/anexo +<C-l> = :open-link <space> # Abrir link presente no corpo do email + +f = :forward<Enter> # Encaminhar mensagem (Forward) +rr = :reply -a<Enter> # Responder a todos +rq = :reply -aq<Enter> # Responder a todos com citação +Rr = :reply<Enter> # Responder ao remetente +Rq = :reply -q<Enter> # Responder ao remetente com citação + +H = :toggle-headers<Enter> # Exibir/ocultar cabeçalhos (headers) completos +<C-k> = :prev-part<Enter> # Parte anterior da mensagem (para emails multipart/HTML/Anexos) +<C-Up> = :prev-part<Enter> # Parte anterior da mensagem +<C-j> = :next-part<Enter> # Próxima parte da mensagem +<C-Down> = :next-part<Enter> # Próxima parte da mensagem +J = :next<Enter> # Ler a próxima mensagem da lista +<C-Right> = :next<Enter> # Ler a próxima mensagem da lista +K = :prev<Enter> # Ler a mensagem anterior da lista +<C-Left> = :prev<Enter> # Ler a mensagem anterior da lista + +# ============================================================================== +# MODO PASSTHROUGH (Quando teclas são passadas direto para o paginador como `less`) +# ============================================================================== +[view::passthrough] +$noinherit = true # Não herda atalhos globais +$ex = <C-x> # Atalho para executar comandos +<Esc> = :toggle-key-passthrough<Enter> # Sair do modo passthrough e voltar ao controle normal do aerc + +# ============================================================================== +# TELA DE COMPOSIÇÃO DE MENSAGEM (Fora do Terminal Embutido) +# ============================================================================== +[compose] +# Atalhos usados ao focar os campos de cabeçalho (Para, Assunto, etc), +# antes de entrar no editor de texto. +$noinherit = true +$ex = <C-x> +$complete = <C-o> # Preenchimento automático (autocomplete) dos contatos + +<C-k> = :prev-field<Enter> # Campo anterior (ex: do Assunto para o Para) +<C-Up> = :prev-field<Enter> # Campo anterior +<C-j> = :next-field<Enter> # Próximo campo +<C-Down> = :next-field<Enter> # Próximo campo +<tab> = :next-field<Enter> # Próximo campo +<backtab> = :prev-field<Enter> # Campo anterior + +<A-p> = :switch-account -p<Enter> # Trocar para a conta de email anterior +<C-Left> = :switch-account -p<Enter> # Trocar para a conta de email anterior +<A-n> = :switch-account -n<Enter> # Trocar para a próxima conta de email +<C-Right> = :switch-account -n<Enter> # Trocar para a próxima conta de email + +<C-p> = :prev-tab<Enter> # Aba anterior +<C-PgUp> = :prev-tab<Enter> # Aba anterior +<C-n> = :next-tab<Enter> # Próxima aba +<C-PgDn> = :next-tab<Enter> # Próxima aba + +# ============================================================================== +# TELA DE COMPOSIÇÃO DE MENSAGEM (Dentro do Editor - Ex: Vim / Nano) +# ============================================================================== +[compose::editor] +# Atalhos usados enquanto você está efetivamente digitando o corpo do email +# dentro do terminal embutido. +$noinherit = true +$ex = <C-x> + +<C-k> = :prev-field<Enter> # Sair do texto e voltar para o campo anterior +<C-Up> = :prev-field<Enter> # Sair do texto e voltar para o campo anterior +<C-j> = :next-field<Enter> # Pular para o próximo campo (se houver) +<C-Down> = :next-field<Enter> # Pular para o próximo campo + +<C-p> = :prev-tab<Enter> # Aba anterior +<C-PgUp> = :prev-tab<Enter> # Aba anterior +<C-n> = :next-tab<Enter> # Próxima aba +<C-PgDn> = :next-tab<Enter> # Próxima aba + +# ============================================================================== +# TELA DE REVISÃO (Antes de Enviar) +# ============================================================================== +[compose::review] +# Atalhos usados na tela de revisão da mensagem após sair do editor de texto. +# (Teclas de uma única letra para ações rápidas) + +y = :send<Enter> # Enviar email +n = :abort<Enter> # Abortar (descarta a mensagem sem perguntar) +s = :sign<Enter> # Ativar/Desativar assinatura criptográfica (PGP) +x = :encrypt<Enter> # Ativar/Desativar criptografia para os destinatários +v = :preview<Enter> # Pré-visualizar a mensagem como o destinatário verá +p = :postpone<Enter> # Adiar (salvar nos Rascunhos / Drafts) +q = :choose -o d discard abort -o p postpone postpone<Enter> # Pergunta se quer deletar ou adiar ao sair +e = :edit<Enter> # Voltar a editar (corpo e cabeçalhos) +a = :attach<space> # Adicionar um anexo +d = :detach<space> # Remover um anexo + +# ============================================================================== +# EMULADOR DE TERMINAL EMBUTIDO +# ============================================================================== +[terminal] +# Configurações de atalhos para quando uma aba for um Terminal comum +$noinherit = true +$ex = <C-x> + +<C-p> = :prev-tab<Enter> # Aba anterior +<C-n> = :next-tab<Enter> # Próxima aba +<C-PgUp> = :prev-tab<Enter> # Aba anterior +<C-PgDn> = :next-tab<Enter> # Próxima aba diff --git a/.config/git/config b/.config/git/config @@ -0,0 +1,12 @@ +[user] + email = git@sivaldodavi.com + name = Sivaldo +[push] + autoSetupRemote = true +[alias] + s = status -s + st = status + ci = commit + cam = commit -am + p = push + lg = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit diff --git a/.config/lf/icons b/.config/lf/icons @@ -0,0 +1,76 @@ +# lf icons + +# file types +fi 📄 +di 📁 +ex 🖥️ +ln 🔗 +or ❌ +ow 📂 +so 🔌 +bd 🧱 +st 🏷️ +pi 🗃️ +cd 🔤 +sg 👥 +su 🧑 + +# file extensions +*.txt 📝 +*.md 📝 +*.png 🖼️ +*.jpg 🖼️ +*.jpeg 🖼️ +*.gif 🖼️ +*.tif 🖼️ +*.tiff 🖼️ +*.webp 🖼️ +*.ico 🖼️ +*.svg 🖼️ +*.jxl 🖼️ +*.bmp 🖼️ +*.mp3 🔊 +*.opus 🔊 +*.ogg 🔊 +*.m4a 🔊 +*.flac 🔊 +*.wav 🔊 +*.mov 📹 +*.webm 📹 +*.mp4 📹 +*.m4v 📹 +*.mpeg 📹 +*.mpg 📹 +*.avi 📹 +*.flv 📹 +*.mkv 📹 +*.html 🌐 +*.gpg 🔒 +*.css 🎨 +*.ps 📚 +*.pdf 📚 +*.djvu 📚 +*.tex 📜 +*.ms 📜 +*.mom 📜 +*.me 📜 +*.bib 🎓 +*.tar 📦 +*.tgz 📦 +*.zip 📦 +*.xz 📦 +*.bz 📦 +*.gz 📦 +*.7z 📦 +*.rar 📦 +*.iso 📀 +*.img 📀 +*.log 📑 +*.1 📑 +*.torrent 📥 + +# code extensions +*.py 🐍 +*.rs 🦀 +*.go 🦦 +*.pl 🐪 diff --git a/.config/lf/lfrc b/.config/lf/lfrc @@ -0,0 +1,75 @@ + +# ============================================================================== +# CONFIGURAÇÕES BÁSICAS +# ============================================================================== +set icons true +set period 1 +set relativenumber +set incsearch +set incfilter +set truncatechar '…' +set scrolloff 10 +set autoquit true +set shellopts '-u' +set hidden true + +# ============================================================================== +# COMANDOS CUSTOMIZADOS +# ============================================================================== + +# Criação de arquivos e pastas +cmd mkdir $mkdir -p "$@" +cmd touch $touch "$@" + +# Abrir arquivos +cmd open $open "$f" + +# Bulkrename +cmd bulkrename ${{ + old_list=$(mktemp) + new_list=$(mktemp) + + # Coleta nomes dos arquivos + if [ -n "$fs" ]; then + for i in $fs; do echo "${i##*/}" >> "$old_list"; done + else + ls -1 > "$old_list" + fi + + cp "$old_list" "$new_list" + $EDITOR "$new_list" + + # Validação de integridade + if [ "$(wc -l < "$old_list")" -ne "$(wc -l < "$new_list")" ]; then + echo "Erro: A contagem de arquivos mudou. Operação cancelada." + rm "$old_list" "$new_list" + read -r _ + exit 1 + fi + + # Renomeação linha a linha + exec 3<"$old_list" + exec 4<"$new_list" + while read -u 3 src && read -u 4 dest; do + if [ "$src" != "$dest" ] && [ ! -e "$dest" ]; then + mv -- "$src" "$dest" + fi + done + + rm "$old_list" "$new_list" + lf -remote "send $id reload" + lf -remote "send $id unselect" +}} + +# ============================================================================== +# ATALHOS (KEYBINDINGS) +# ============================================================================== + +map D delete +map <c-r> reload +map B bulkrename +map l open +map <enter> open + +# Atalho para mostrar/esconder arquivos ocultos +map . set hidden! diff --git a/.config/nsxiv/exec/key-handler b/.config/nsxiv/exec/key-handler @@ -0,0 +1,10 @@ +#!/bin/sh + +# Handle custom keys for nsxiv + +while read -r file +do + case "$1" in + "y") xclip -selection clipboard -target image/png "$file" & ;; + esac +done diff --git a/.config/shell/ashrc b/.config/shell/ashrc @@ -0,0 +1,90 @@ +alias v="vim" +alias sudo="doas" +alias bxiv='echo "Nsxiv.window.background: black" | xrdb -merge && nsxiv -b' +alias abook='/usr/bin/abook --datafile "$XDG_DATA_HOME/abook/addressbook"' +alias fk='ps aux | fzf --height 40% --reverse | awk "{print \$2}" | xargs kill -9' +alias hf='[ -f "$HISTFILE" ] && tac "$HISTFILE" | awk "!seen[\$0]++" | fzf --no-multi | xclip -selection clipboard' + +# --------------------------- +# DIRETÓRIOS +# --------------------------- +alias cac="cd $XDG_CACHE_HOME" +alias cf="cd $XDG_CONFIG_HOME" +alias D="cd $XDG_DOWNLOAD_DIR" +alias d="cd $XDG_DOCUMENTS_DIR" +alias dt="cd $XDG_DATA_HOME" +alias h="cd $HOME" +alias n="cd $DIR_NOTES" +alias m="cd $XDG_MUSIC_DIR" +alias mn="cd /mnt" +alias pp="cd $XDG_PICTURES_DIR" +alias sc="cd $HOME/.local/bin" +alias src="cd $HOME/.local/src" +alias vv="cd $XDG_VIDEOS_DIR" +alias dev="cd $HOME/.local/dev" + +# --------------------------- +# FUNÇÕES +# --------------------------- + +net() { + case "${1:-}" in + on) doas ip link set "${2:-wlan0}" up ;; + off) doas ip link set "${2:-wlan0}" down ;; + reset) doas udhcpc -i "${2:-wlan0}" -R ;; + usb) doas ip link set usb0 up && doas udhcpc -i usb0 ;; + *) + choice=$(printf 'on\noff\nreset\nusb\n' | fzf --prompt="net: " --height=40% --reverse) + [ -z "$choice" ] && return 0 + net "$choice" "$2" + ;; + esac +} + +kp() { + _preview='apk info -d {1}' + case "${1:-}" in + add) apk search -v | fzf -m --preview "$_preview" | awk '{print $1}' | xargs -r doas apk add ;; + del) apk info | sort | fzf -m --preview "$_preview" | xargs -r doas apk del ;; + search) apk search -v | fzf --preview "$_preview" | awk '{print $1}' ;; + up) doas apk update && doas apk upgrade ;; + *) + choice=$(printf 'add\ndel\nsearch\nup\n' | fzf --prompt="kp: " --height=40% --reverse) + [ -z "$choice" ] && return 0 + kp "$choice" + ;; + esac +} + +md() { + case "${1:-}" in + edit) find . -maxdepth 1 -name "*.md" | sort | fzf --preview "bat --color=always {} 2>/dev/null || cat {}" | xargs -r $EDITOR ;; + rm) find . -maxdepth 1 -name "*.md" | sort | fzf -m --preview "bat --color=always {} 2>/dev/null || cat {}" | xargs -r rm -i ;; + *) + choice=$(printf 'edit\nrm\n' | fzf --prompt="md: " --height=40% --reverse) + [ -z "$choice" ] && return 0 + md "$choice" + ;; + esac +} + +cfg() { + CFG="${XDG_CONFIG_HOME:-$HOME/.config}" + case "${1:-}" in + vim) target="$CFG/vim/vimrc" ;; + shell) target="$CFG/shell/ashrc" ;; + profile) target="$CFG/X11/profile" ;; + xinitrc) target="$CFG/X11/xinitrc" ;; + lf) target="$CFG/lf/lfrc" ;; + aerc) target="$CFG/aerc/aerc.conf" ;; + *) + choice=$(printf 'vim\nshell\nprofile\nxinitrc\nlf\naerc\n' | fzf --prompt="cfg: " --height=40% --reverse) + [ -z "$choice" ] && return 0 + cfg "$choice" + return + ;; + esac + + [ -f "$target" ] || { echo "cfg: arquivo não encontrado: $target" >&2; return 1; } + "${EDITOR:-vi}" "$target" +} diff --git a/.config/vim/vimrc b/.config/vim/vimrc @@ -0,0 +1,108 @@ + +let mapleader=" " +let g:plugins = [ + \ "https://github.com/vimwiki/vimwiki.git", + \ "https://github.com/ap/vim-css-color.git", + \ "https://github.com/junegunn/goyo.vim.git", + \ "https://github.com/tpope/vim-surround.git", + \ "https://github.com/tpope/vim-commentary.git", + \ "https://github.com/gpanders/vim-scdoc.git", + \ "https://github.com/preservim/nerdtree.git", + \ "https://github.com/jreybert/vimagit.git", + \ "https://github.com/kaarmu/typst.vim.git", + \ "https://github.com/vim-scripts/SyntaxRange.git"] + +" ─── Aparência ─────────────────────────────────────────────── +set title +set t_Co=256 +set laststatus=2 +set number relativenumber +set nocursorline +set nocursorcolumn +set colorcolumn=0 +set notermguicolors + +" ─── Comportamento base ─────────────────────────────────────── +set nocompatible +filetype plugin on +syntax on +set encoding=utf-8 +set ttyfast +set nofoldenable +set nobackup +set noswapfile +set notimeout +set mouse=a +set hlsearch + +" ─── Clipboard (sistema via xclip) ────────────────────────── +vnoremap <leader>y :w !xclip -selection clipboard<CR><CR> +nnoremap <leader>Y :%w !xclip -selection clipboard<CR><CR> + +" ─── Exibição de texto ─────────────────────────────────────── +set wrap +set linebreak + +" ─── Autocompletar e formatação ────────────────────────────── +set wildmode=longest,list,full +autocmd FileType * setlocal formatoptions-=c formatoptions-=r formatoptions-=o + +" ─── Splits ────────────────────────────────────────────────── +set splitbelow splitright +nnoremap <C-h> <C-w>h +nnoremap <C-j> <C-w>j +nnoremap <C-k> <C-w>k +nnoremap <C-l> <C-w>l + +" ─── Atalhos gerais ────────────────────────────────────────── +nnoremap Y y$ +nnoremap cc :center<CR> +nnoremap <leader>p :Ex<CR> + +" Remover espaços em branco finais +nnoremap <silent> <leader>t :let _s=@/<Bar>:%s/\s\+$//e<Bar>:let @/=_s<Bar>:nohl<CR> + +" ─── Goyo ──────────────────────────────────────────────────── +nnoremap <leader>f :Goyo \| set linebreak<CR> +nnoremap <leader>g :Goyo!<CR> + +" ─── Ortografia ────────────────────────────────────────────── +nnoremap <leader>s :setlocal spell! spelllang=pt_br<CR> + +" ─── NERDTree ──────────────────────────────────────────────── +nnoremap <leader>n :NERDTreeToggle<CR> + +" ─── Typst ─────────────────────────────────────────────────── +autocmd FileType typst nnoremap <buffer> <leader>c :w<CR>:!typst compile "%" --root .<CR> +autocmd FileType typst nnoremap <buffer> <leader>v :!zathura "%:p:r.pdf" &<CR> +autocmd FileType typst nnoremap <buffer> <leader>o :w<CR>:!typst compile "%" --root . && zathura "%:p:r.pdf" &<CR> +autocmd FileType typst nnoremap <buffer> <leader>W :!typst watch "%" --root . &<CR> + +" ─── VimWiki ───────────────────────────────────────────────── +let g:vimwiki_list = [{ + \ 'path': '~/notes/', + \ 'syntax': 'markdown', + \ 'ext': '.md', + \ 'diary_rel_path': 'diary/' + \ }] +nnoremap <leader>w <cmd>VimwikiMakeDiaryNote<cr> + +" ─── Defaults por tipo de arquivo ──────────────────────────── +autocmd FileType markdown,vimwiki setlocal tw=0 et ts=2 sw=2 +autocmd FileType text setlocal tw=0 + +" ─── Sintaxe para arquivos específicos ─────────────────────── +autocmd BufRead,BufNewFile *.ms,*.me,*.mom,*.man set filetype=groff + +" ─── Alternar barra de status ──────────────────────────────── +let s:bar_hidden = 0 +function! ToggleBar() + if s:bar_hidden == 0 + let s:bar_hidden = 1 + set noshowmode noruler laststatus=0 noshowcmd + else + let s:bar_hidden = 0 + set showmode ruler laststatus=2 showcmd + endif +endfunction +nnoremap <leader>b :call ToggleBar()<CR> diff --git a/.gitignore b/.gitignore @@ -0,0 +1,45 @@ +* + +!README.md +!.gitignore + +# --------------------------- +# .LOCAL +# --------------------------- + +!.local/ +/.local/* + +# scripts +!/.local/bin/ +!/.local/bin/* + +# --------------------------- +# .CONFIG +# --------------------------- + +!.config +/.config/* + +!/.config/X11/ +!/.config/X11/** +/.config/X11/xauthority + +!/.config/aerc/ +!/.config/aerc/** +/.config/aerc/accounts.conf + +!/.config/lf/ +!/.config/lf/** + +!/.config/git/ +!/.config/git/* + +!/.config/nsxiv/ +!/.config/nsxiv/** + +!/.config/shell/ +!/.config/shell/** + +!/.config/vim/ +!/.config/vim/vimrc diff --git a/.local/bin/backup b/.local/bin/backup @@ -0,0 +1,94 @@ +#!/bin/sh + +# --- Configurações XDG --- +XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" +XDG_DOCUMENTS_DIR="${XDG_DOCUMENTS_DIR:-$HOME/documents}" +XDG_MUSIC_DIR="${XDG_MUSIC_DIR:-$HOME/music}" +XDG_PICTURES_DIR="${XDG_PICTURES_DIR:-$HOME/pictures}" +XDG_VIDEOS_DIR="${XDG_VIDEOS_DIR:-$HOME/videos}" + +REMOTE="disroot" +RCLONE_BASE_URL="https://cloud.disroot.org/remote.php/dav/files" +ACTION="${1:-}" + +# --- 1. Verificação de dependências --- +MISSING="" +command -v rclone >/dev/null 2>&1 || MISSING="$MISSING rclone" +command -v rsync >/dev/null 2>&1 || MISSING="$MISSING rsync" + +if [ -n "$MISSING" ]; then + echo "Instalando ferramentas de sincronização:$MISSING..." + doas apk add $MISSING || { echo "Erro ao instalar dependências." >&2; exit 1; } +fi + +# --- 2. Validação do comando --- +case "$ACTION" in + login|push|pull) ;; + *) + echo "Uso: $(basename "$0") [login|push|pull]" + exit 1 + ;; +esac + +# --- 3. Comando: LOGIN --- +if [ "$ACTION" = "login" ]; then + echo "Configurando acesso ao $REMOTE..." + printf "Usuário Disroot: " && read -r R_USER + printf "Senha Disroot: " + stty -echo && read -r R_PASS && stty echo && echo "" + + OBSCURED=$(rclone obscure "$R_PASS") || exit 1 + + rclone config create "$REMOTE" webdav \ + url="$RCLONE_BASE_URL/$R_USER" \ + vendor="nextcloud" \ + user="$R_USER" \ + pass="$OBSCURED" \ + --non-interactive + + echo "Configurado com sucesso!" + exit 0 +fi + +# --- 4. Verificação de segurança --- +if ! rclone listremotes | grep -q "^${REMOTE}:$"; then + echo "Erro: Controle remoto '$REMOTE' não encontrado. Rode: $(basename "$0") login" >&2 + exit 1 +fi + +echo "Iniciando: $ACTION..." + +# --- 5. Sincronização dos diretórios --- +set -- \ + "documents:$XDG_DOCUMENTS_DIR" \ + "music:$XDG_MUSIC_DIR" \ + "pictures:$XDG_PICTURES_DIR" \ + "videos:$XDG_VIDEOS_DIR" \ + "abook:$XDG_DATA_HOME/abook" + +for entry; do + rem="${entry%%:*}" + loc="${entry#*:}" + + if [ "$ACTION" = "push" ]; then + rclone sync "$loc" "$REMOTE:$rem" \ + --delete-excluded \ + --backup-dir="$REMOTE:trash/$(date +%Y-%m-%d)/$rem" \ + -P + else + mkdir -p "$loc" + rclone sync "$REMOTE:$rem" "$loc" -P + fi +done + +# --- 6. Sincronização do 'bookmarks' --- +BOOKMARKS_LOC="$XDG_DATA_HOME/bookmarks" + +if [ "$ACTION" = "push" ]; then + [ -f "$BOOKMARKS_LOC" ] && rclone copyto "$BOOKMARKS_LOC" "$REMOTE:bookmarks" -P +else + mkdir -p "$(dirname "$BOOKMARKS_LOC")" + rclone copyto "$REMOTE:bookmarks" "$BOOKMARKS_LOC" -P +fi + +echo "Sincronização ($ACTION) concluída!" diff --git a/.local/bin/bt b/.local/bin/bt @@ -0,0 +1,88 @@ +#!/bin/ash + +# Configuração de ambiente +TERMINAL=${TERMINAL:-st} + +clear +printf '%s\n' "--- Bluetooth Manager ---" + +# 1. Gerenciamento de dependências (verifica e instala) +MISSING="" +apk info -e bluez >/dev/null 2>&1 || MISSING="$MISSING bluez" +apk info -e bluez-deprecated >/dev/null 2>&1 || MISSING="$MISSING bluez-deprecated" + +if [ -n "$MISSING" ]; then + printf "📦 Instalando pacotes Bluetooth:%s\n" "$MISSING" + doas apk add $MISSING >/dev/null || { printf "ERRO ao instalar dependências.\n"; exit 1; } +fi + +# 2. Gerenciamento idempotente do serviço +if ! rc-service bluetooth status >/dev/null 2>&1; then + printf "⚙️ Iniciando serviço bluetooth...\n" + doas rc-service bluetooth start >/dev/null +fi +doas rc-update add bluetooth default >/dev/null 2>&1 + +# Inicializa o adaptador silenciosamente +bluetoothctl power on >/dev/null 2>&1 + +printf '%s\n' "Escaneando dispositivos (5s)..." +bluetoothctl --timeout 5 scan on + +# Obtenção da lista de dispositivos disponíveis +LIST=$(bluetoothctl devices | sed 's/^Device //') + +if [ -z "$LIST" ]; then + printf '%s\n' "Nenhum dispositivo encontrado." + exit 0 +fi + +# Interface de seleção do dispositivo alvo +DEVICE=$(printf '%s\n' "$LIST" | dmenu -i -l 15 -p "Bluetooth:") + +if [ -z "$DEVICE" ]; then + printf '%s\n' "Seleção cancelada." + exit 0 +fi + +# Extração isolada do MAC address +MAC=$(printf '%s\n' "$DEVICE" | awk '{print $1}') + +printf 'Processando: %s\n' "$DEVICE" + +# Verificação de pareamento prévio para otimização de fluxo +PAIRED=$(bluetoothctl devices Paired | awk '{print $2}') + +if printf '%s\n' "$PAIRED" | grep -qx "$MAC"; then + printf '%s\n' "Dispositivo já pareado. Iniciando conexão..." +else + printf '%s\n' "Dispositivo não pareado. Abrindo console interativo..." + + # Inicia processo de pareamento em terminal secundário para permitir confirmação manual (ex: PIN) + "$TERMINAL" -e sh -c " + printf '%s\n' '=========================================' + printf '%s\n' ' AUTENTICAÇÃO BLUETOOTH ' + printf '%s\n' '=========================================' + printf 'Dispositivo: %s\n\n' '$DEVICE' + printf '%s\n' '1. O código de pareamento será exibido abaixo.' + printf '%s\n' '2. Confirme a operação no dispositivo móvel.' + printf '%s\n' '3. Se solicitado [yes/no], digite: yes' + printf '%s\n' '4. Para encerrar, digite: exit' + printf '%s\n' '-----------------------------------------' + + # Aciona o pareamento de forma assíncrona para não bloquear o carregamento do prompt + (sleep 1; bluetoothctl pair '$MAC') & + + # Delega o controle ao console interativo + bluetoothctl + " +fi + +# Pós-processamento: estabelecimento de confiança e conexão final +bluetoothctl trust "$MAC" >/dev/null 2>&1 + +if bluetoothctl connect "$MAC" >/dev/null 2>&1; then + printf 'Conectado com sucesso a %s\n' "$DEVICE" +else + printf 'Falha na conexão. Verifique o status do dispositivo.\n' +fi diff --git a/.local/bin/clip b/.local/bin/clip @@ -0,0 +1,39 @@ +#!/bin/sh + +HIST="${XDG_CACHE_HOME:-$HOME/.cache}/clipboard-history" + +_daemon() { + mkdir -p "$(dirname "$HIST")" + touch "$HIST" + last="" + while true; do + current=$(xclip -o -selection clipboard 2>/dev/null) + if [ -n "$current" ] && [ "$current" != "$last" ]; then + last="$current" + tmp=$(mktemp) + printf '%s\n---\n' "$current" | cat - "$HIST" > "$tmp" + mv "$tmp" "$HIST" + fi + sleep 1 + done +} + +_pick() { + [ -f "$HIST" ] || { echo "Histórico vazio."; exit 1; } + + chosen=$(awk ' + /^---$/ { if (entry != "") print entry; entry=""; next } + { entry = (entry == "") ? $0 : entry " · " $0 } + END { if (entry != "") print entry } + ' "$HIST" | dmenu -l 15 -p "Clipboard:") + + [ -z "$chosen" ] && exit 0 + + printf '%s' "$chosen" | sed 's/ · /\n/g' | xclip -selection clipboard +} + +case "${1:-pick}" in + daemon) _daemon ;; + pick) _pick ;; + *) printf 'Uso: %s [daemon|pick]\n' "$(basename "$0")" >&2; exit 1 ;; +esac diff --git a/.local/bin/culto b/.local/bin/culto @@ -0,0 +1,439 @@ +#!/usr/bin/env bash + +# ============================================================================== +# Configuração +# ============================================================================== + +MUSICAS_URL="https://codeberg.org/sivaldodavi/ipp/archive/main.tar.gz" +PIX_URL="https://codeberg.org/sivaldodavi/ipp/raw/branch/main/pix-igreja.png" + +CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/culto" +BIBLE_FILE="$CACHE_DIR/biblia" +BIBLE_URL="https://codeberg.org/sivaldodavi/ipp/raw/branch/main/biblia" +MUSICAS_DIR="$CACHE_DIR/musicas" +PIX_FILE="$CACHE_DIR/pix.png" +SENT_FILE="/tmp/sent_presentation" + +: "${EDITOR:=vi}" + +LITURGIA_FIXA=( + "T: oração inicial" + "T: oração de contrição" + "T: oração pelas crianças" + "T: oração pela palavra" + "T: ceia" + "T: ofertório" + "T: oração final" + "T: boas vindas" + "AG: agenda" + "C: limpar tela" +) + +AGENDA_TEXTO="Terça (19:30) - Culto de oração +Quinta (19:30) - Culto de doutrina +Domingo (08:30) - EBD +Domingo (18:00) - Culto solene" + +# ============================================================================== +# Utilitários +# ============================================================================== + +# Verifica/Instala o xrandr +if ! command -v xrandr >/dev/null 2>&1; then + echo "'xrandr' não encontrado. Instalando..." >&2 + doas apk add xrandr || { echo "ERRO: Falha ao instalar xrandr." >&2; exit 1; } +fi + +_download() { + local url="$1" dest="$2" + if command -v curl >/dev/null 2>&1; then + curl -fsSL "$url" -o "$dest" + elif command -v wget >/dev/null 2>&1; then + wget -q "$url" -O "$dest" + else + echo "Erro: curl ou wget necessário." >&2 + return 1 + fi +} + +# ============================================================================== +# Bíblia +# ============================================================================== + +_ensure_bible() { + [ -f "$BIBLE_FILE" ] && return 0 + mkdir -p "$(dirname "$BIBLE_FILE")" + echo "Baixando bíblia..." >&2 + _download "$BIBLE_URL" "$BIBLE_FILE" +} + +_bible_python() { + BIBLE_FILE="$BIBLE_FILE" python3 - "$@" <<'PYEOF' +import os, re, sys + +BIBLE_FILE = os.environ["BIBLE_FILE"] +SLIDE_WIDTH = 180 + +USE_COLOR = sys.stdout.isatty() +def _c(code, t): return f"\033[{code}m{t}\033[0m" if USE_COLOR else t +def azul(t): return _c("1;34", t) +def amarelo(t): return _c("1;33", t) +def verde(t): return _c("1;32", t) +def vermelho(t): return _c("1;31", t) + +def load_bible(): + verses = [] + with open(BIBLE_FILE, encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line in ("ERV", "English Revised Version"): + continue + m = re.match(r"^([\w\s]+)\s+(\d+):(\d+)\t(.+)$", line) + if m: + book, chapter, verse, text = m.groups() + verses.append({"book": book, "chapter": int(chapter), + "verse": int(verse), "text": text}) + return verses + +def parse_ref(query, verses): + query = query.strip() + parts = query.rpartition(" ") + book, num = parts[0].strip(), parts[2].strip() + if not book: + book, num = num, "" + try: + if ":" in num: + cap_str, vrs_part = num.split(":", 1) + cap_num = int(cap_str) + if "-" in vrs_part: + v1, v2 = (int(x) for x in vrs_part.split("-", 1)) + else: + v1 = v2 = int(vrs_part) + return [v for v in verses + if v["book"].lower().startswith(book.lower()) + and v["chapter"] == cap_num + and v1 <= v["verse"] <= v2] + else: + cap_num = int(num) if num.isdigit() else 0 + return [v for v in verses + if v["book"].lower().startswith(book.lower()) + and (cap_num == 0 or v["chapter"] == cap_num)] + except ValueError: + return [] + +def _chunks(text, width): + words, chunks, current = text.split(), [], "" + for word in words: + if len(current) + len(word) + 1 > width: + chunks.append(current.rstrip()) + current = word + " " + else: + current += word + " " + if current.strip(): + chunks.append(current.rstrip()) + return chunks + +def slides_read(rows, ref_label, no_cover, resp): + out = [] + if not no_cover: + out += [ref_label.upper(), ""] + if resp: + for i, row in enumerate(rows): + label = "DIRIGENTE" if i % 2 == 0 else "IGREJA" + out += [f"{label}\n{row['verse']}. {row['text']}", ""] + else: + full_text = " ".join(f"{r['verse']}. {r['text']}" for r in rows) + chunks = _chunks(full_text, SLIDE_WIDTH) + for i, chunk in enumerate(chunks): + pre = "... " if i > 0 else "" + pos = " ..." if i < len(chunks) - 1 else "" + out += [f"{pre}{chunk}{pos}", ""] + return "\n".join(out) + +def terminal_read(rows, ref_label): + USE_COLOR = sys.stdout.isatty() + print(azul(ref_label)) + print() + for r in rows: + num = verde(f"{r['verse']:3}.") + print(f" {num} {r['text']}") + print() + +cmd = sys.argv[1] +rest = sys.argv[2:] + +if cmd == "read": + no_cover = "--no-cover" in rest + resp = "--resp" in rest + query = " ".join(a for a in rest if not a.startswith("--")) + verses = load_bible() + rows = parse_ref(query, verses) + if not rows: + print(vermelho(f"Nenhuma referência encontrada para '{query}'."), file=sys.stderr) + sys.exit(1) + num_part = query.rpartition(" ")[2] + ref_label = f"{rows[0]['book']} {num_part}" if num_part else rows[0]["book"] + print(slides_read(rows, ref_label, no_cover, resp)) + +elif cmd == "terminal": + query = " ".join(rest) + verses = load_bible() + rows = parse_ref(query, verses) + if not rows: + print(vermelho(f"Nenhuma referência encontrada para '{query}'."), file=sys.stderr) + sys.exit(1) + num_part = query.rpartition(" ")[2] + ref_label = f"{rows[0]['book']} {num_part}" if num_part else rows[0]["book"] + terminal_read(rows, ref_label) + +elif cmd == "search": + word = " ".join(rest) + verses = load_bible() + results = [v for v in verses if word.lower() in v["text"].lower()] + pattern = re.compile(re.escape(word), re.IGNORECASE) + print(azul(f"{len(results)} ocorrência(s) de '{amarelo(word)}':")) + print() + for r in results: + ref = f"{r['book']} {r['chapter']}:{r['verse']}" + highlighted = pattern.sub(lambda m: vermelho(m.group(0)), r["text"]) + print(f"{verde(f'{ref:25}')} {highlighted}") + +PYEOF +} + +# ============================================================================== +# Integração com o 'sent' +# ============================================================================== + +format_sent() { + awk '{ print toupper($0) }' \ + | fold -s -w 40 \ + | sed 's/[[:space:]]*$//' \ + > "$SENT_FILE" + + if pgrep -x "sent" >/dev/null; then + xdotool search --class sent key r + else + nohup sent "$SENT_FILE" >/dev/null 2>&1 & + fi +} + +clear_sent() { + printf ' ' > "$SENT_FILE" + pgrep -x "sent" >/dev/null && xdotool search --class sent key r +} + +# ============================================================================== +# Músicas +# ============================================================================== + +ensure_musicas() { + if [ ! -d "$MUSICAS_DIR" ] || [ -z "$(ls -A "$MUSICAS_DIR"/*.md 2>/dev/null)" ]; then + echo "Baixando hinário..." >&2 + local tmp_tar="$CACHE_DIR/musicas.tar.gz" + mkdir -p "$CACHE_DIR" + _download "$MUSICAS_URL" "$tmp_tar" || return 1 + rm -rf "$MUSICAS_DIR" + mkdir -p "$MUSICAS_DIR" + tar -xzf "$tmp_tar" -C "$MUSICAS_DIR" --strip-components=1 2>/dev/null \ + || tar -xzf "$tmp_tar" -C "$MUSICAS_DIR" + find "$MUSICAS_DIR" -mindepth 2 -name "*.md" -exec mv {} "$MUSICAS_DIR/" \; 2>/dev/null + rm -f "$tmp_tar" + fi +} + +_song_title() { + basename "$1" .md | sed -E 's/^[0-9]+[a-z]?\s*-\s*//; s/\s*\(.*\)\s*$//' +} + +_show_md_song() { + local filepath="$1" + local title + title=$(_song_title "$filepath") + + { + printf '%s\n\n\n' "$title" + + python3 - "$filepath" <<'PYEOF' +import sys, re + +with open(sys.argv[1], encoding='utf-8') as f: + content = f.read() + +content = re.sub(r'^#.*\n?', '', content, flags=re.MULTILINE) + +for block in re.split(r'\n{2,}', content.strip()): + block = block.strip() + if block: + print(block) + print("\n") +PYEOF + } | format_sent +} + +pick_song() { + ensure_musicas || return 1 + + local chosen + chosen=$(ls "$MUSICAS_DIR"/*.md 2>/dev/null \ + | xargs -I{} basename {} .md \ + | sed -E 's/^[0-9]+[a-z]?\s*-\s*//; s/\s*\(.*\)\s*$//' \ + | sort \ + | fzf --reverse --prompt="Música: ") + + [ -z "$chosen" ] && return 0 + + local match + match=$(ls "$MUSICAS_DIR"/*.md 2>/dev/null \ + | while IFS= read -r f; do + t=$(_song_title "$f") + [ "$t" = "$chosen" ] && printf '%s\n' "$f" && break + done) + + if [ -n "$match" ]; then + _show_md_song "$match" + else + echo "Música não encontrada: $chosen" >&2 + fi +} + +song_by_name() { + ensure_musicas || return 1 + local query="${1,,}" + + local match + match=$(ls "$MUSICAS_DIR"/*.md 2>/dev/null \ + | while IFS= read -r f; do + t=$(_song_title "$f" | tr '[:upper:]' '[:lower:]') + [[ "$t" == *"$query"* ]] && printf '%s\n' "$f" && break + done) + + if [ -n "$match" ]; then + _show_md_song "$match" + else + echo "Música não encontrada: $1" >&2 + fi +} + +# ============================================================================== +# Bíblia — wrappers para sent +# ============================================================================== + +get_bible() { + local query="$1" + local no_cover="$2" + local resp="$3" + + local flags="" + [ "$no_cover" = "true" ] && flags="$flags --no-cover" + [ "$resp" = "true" ] && flags="$flags --resp" + + # shellcheck disable=SC2086 + _ensure_bible || exit 1 + _bible_python read $flags "$query" | format_sent +} + +# ============================================================================== +# Liturgia fixa (menu fzf) +# ============================================================================== + +show_menu() { + while true; do + local sel + sel=$(printf '%s\n' "${LITURGIA_FIXA[@]}" \ + | fzf --reverse --prompt="Liturgia: ") + [ -z "$sel" ] && break + + local tp val + tp="${sel%%:*}" + val=$(printf '%s' "${sel#*:}" | sed 's/^[[:space:]]*//') + + case "$tp" in + T) printf '%s' "$val" | format_sent ;; + AG) printf '%s' "$AGENDA_TEXTO" | format_sent ;; + C) clear_sent ;; + *) echo "Tipo desconhecido: $tp" >&2 ;; + esac + done +} + +# ============================================================================== +# Lógica principal +# ============================================================================== + +case "${1:-}" in + m|menu) show_menu ;; + cm) pick_song ;; + song) song_by_name "$2" ;; + + b|bible) get_bible "$2" "false" "false" ;; + bd|bible-clean) get_bible "$2" "true" "false" ;; + r|resp) get_bible "$2" "false" "true" ;; + + v|ver) + _ensure_bible || exit 1 + _bible_python terminal "$2" | less -R -i + ;; + s|search) + _ensure_bible || exit 1 + _bible_python search "$2" | less -R -i + ;; + + t|text) printf '%s' "$2" | format_sent ;; + ag|agenda) printf '%s' "$AGENDA_TEXTO" | format_sent ;; + x|clear) clear_sent ;; + + i|index) + awk 'BEGIN{RS="\n\n+"; c=1} { + gsub(/^\s+|\s+$/, "") + if (length($0) > 0) printf "[%02d] %.40s...\n", c++, $0 + }' "$SENT_FILE" + ;; + + p|pix) + if [ ! -f "$PIX_FILE" ]; then + echo "Baixando imagem PIX..." >&2 + mkdir -p "$CACHE_DIR" + _download "$PIX_URL" "$PIX_FILE" || exit 1 + fi + nohup nsxiv "$PIX_FILE" >/dev/null 2>&1 & + ;; + + bible-reset) + rm -f "$BIBLE_FILE" + _ensure_bible && echo "Bíblia atualizada." + ;; + musicas-reset) + rm -rf "$MUSICAS_DIR" + ensure_musicas && echo "Hinário atualizado." + ;; + pix-reset) + rm -f "$PIX_FILE" + mkdir -p "$CACHE_DIR" + _download "$PIX_URL" "$PIX_FILE" && echo "Imagem PIX atualizada." + ;; + + *) + cat <<'USAGE' +culto <comando> [arg] + + m / menu Liturgia (fzf) + cm Músicas (fzf) + song <nome> Música por nome parcial + b / bible <ref> Bíblia (com capa) + bd <ref> Bíblia (sem capa) + r / resp <ref> Responsivo (DIRIGENTE / IGREJA) + v / ver <ref> Bíblia no terminal + s / search <pal> Buscar na bíblia + t / text <texto> Texto livre + ag / agenda Agenda da semana + i / index Índice dos slides atuais + x / clear Limpar tela do sent + p / pix Abrir QR code PIX + + bible-reset Rebaixar bíblia + musicas-reset Rebaixar hinário + pix-reset Rebaixar imagem PIX +USAGE + ;; +esac diff --git a/.local/bin/draw b/.local/bin/draw @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 +""" +Servidor local minimalista para Excalidraw. +Assets JS ficam em ~/.cache/excalidraw para sobreviver a reboots. +Bibliotecas em ~/.local/share/excalidraw são mescladas automaticamente +num único all-libraries.excalidrawlib servido pelo servidor. +""" + +import json +import mimetypes +import os +import signal +import sys +import urllib.request +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import datetime, timezone +from http.server import HTTPServer, BaseHTTPRequestHandler +from pathlib import Path +from socketserver import ThreadingMixIn + +# ── Configurações ──────────────────────────────────────────────────────────── +PORT = 8080 +CACHE_DIR = Path.home() / ".cache" / "excalidraw" +LIBS_DIR = Path.home() / ".local" / "share" / "excalidraw" +MERGED_LIB = CACHE_DIR / "all-libraries.excalidrawlib" + +ASSETS = { + "react.js": "https://unpkg.com/react@18.2.0/umd/react.production.min.js", + "react-dom.js": "https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js", + "excalidraw.js": "https://unpkg.com/@excalidraw/excalidraw@0.17.6/dist/excalidraw.production.min.js", +} + +EXCALIDRAW_ASSET_PATH = "https://unpkg.com/@excalidraw/excalidraw@0.17.6/dist/" +# ───────────────────────────────────────────────────────────────────────────── + + +def fetch_asset(filename: str, url: str, retries: int = 4) -> None: + dest = CACHE_DIR / filename + if dest.exists() and dest.stat().st_size > 1024: + return + + req = urllib.request.Request( + url, + headers={"User-Agent": "Mozilla/5.0 (compatible; ExcalidrawLocal/1.0)", "Accept": "*/*"}, + ) + + last_exc: Exception = RuntimeError("sem tentativas") + for attempt in range(1, retries + 1): + try: + with urllib.request.urlopen(req, timeout=20) as response: + data = response.read() + if len(data) < 1024: + raise RuntimeError(f"payload suspeito ({len(data)} bytes)") + dest.write_bytes(data) + return + except Exception as exc: + last_exc = exc + wait = 2 ** attempt # 2, 4, 8, 16 s + print(f" ↺ {filename} tentativa {attempt}/{retries} falhou ({exc}); aguardando {wait}s…") + import time; time.sleep(wait) + + raise last_exc + + +def bootstrap_assets() -> None: + CACHE_DIR.mkdir(parents=True, exist_ok=True) + os.chdir(CACHE_DIR) + + with ThreadPoolExecutor(max_workers=3) as pool: + futures = {pool.submit(fetch_asset, name, url): name for name, url in ASSETS.items()} + for future in as_completed(futures): + name = futures[future] + try: + future.result() + print(f" ✓ {name}") + except Exception as exc: + print(f" ✗ {name}: {exc}") + sys.exit(1) + + +def merge_libraries() -> None: + """ + Varre ~/.local/share/excalidraw, mescla todos os .excalidrawlib + num único arquivo em ~/.cache/excalidraw/all-libraries.excalidrawlib. + IDs duplicados são ignorados para o caso de re-execução. + """ + if not LIBS_DIR.is_dir(): + print(" – Diretório de bibliotecas não encontrado, pulando merge.") + return + + files = sorted(LIBS_DIR.glob("*.excalidrawlib")) + if not files: + print(" – Nenhuma biblioteca encontrada em ~/.local/share/excalidraw.") + return + + all_items: list = [] + seen_ids: set = set() + + for f in files: + try: + data = json.loads(f.read_text(encoding="utf-8")) + except Exception as e: + print(f" ✗ {f.name}: {e}") + continue + + version = data.get("version", 1) + + # v2: libraryItems é uma lista plana de elementos individuais (cada um é 1 item). + # v1: library é uma lista de grupos, onde cada grupo é uma lista de elementos + # que formam juntos um único item da biblioteca. + if version >= 2: + raw_items = data.get("libraryItems", []) + else: + # Converte cada grupo de elementos v1 num libraryItem v2. + raw_items = [] + import uuid as _uuid + for group in data.get("library", []): + raw_items.append({ + "id": str(_uuid.uuid4()), + "status": "published", + "elements": group, + "created": 0, + }) + + added = 0 + for item in raw_items: + uid = item.get("id") + if uid in seen_ids: + continue + seen_ids.add(uid) + all_items.append(item) + added += 1 + + print(f" ✓ {f.name:45s} {added:3d} itens (v{version})") + + merged = { + "type": "excalidrawlib", + "version": 2, + "source": f"merged:{datetime.now(timezone.utc).isoformat()}", + "libraryItems": all_items, + } + MERGED_LIB.write_text(json.dumps(merged, ensure_ascii=False), encoding="utf-8") + print(f" → {len(all_items)} itens mesclados → {MERGED_LIB}") + + +class SPAHandler(BaseHTTPRequestHandler): + _html_payload: bytes = b"" + + def _send_headers(self, status: int = 200) -> None: + self.send_response(status) + self.send_header("X-Content-Type-Options", "nosniff") + + def _serve_file(self, filepath: Path) -> None: + try: + data = filepath.read_bytes() + except (OSError, IOError): + self.send_error(404) + return + + self._send_headers(200) + mime, _ = mimetypes.guess_type(filepath.name) + self.send_header("Content-Type", mime or "application/octet-stream") + + if filepath.suffix == ".js": + self.send_header("Cache-Control", "public, max-age=31536000, immutable") + elif filepath.suffix == ".excalidrawlib": + # Lib mesclada pode ser regenerada; não cachear no browser. + self.send_header("Cache-Control", "no-store") + + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + + def _serve_spa(self) -> None: + self._send_headers(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.end_headers() + self.wfile.write(self._html_payload) + + def do_GET(self) -> None: + requested = self.path.lstrip("/") or "index.html" + filepath = CACHE_DIR / requested + if filepath.is_file(): + self._serve_file(filepath) + else: + self._serve_spa() + + def log_message(self, format, *args) -> None: + pass + + +class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): + daemon_threads = True + allow_reuse_address = True + + +def generate_index_html() -> bytes: + if MERGED_LIB.exists(): + bootstrap_js = """ + function startApp(libraryItems) { + var initialData = libraryItems ? { libraryItems: libraryItems } : null; + + var App = function() { + return React.createElement( + "div", + { style: { height: "100vh", width: "100vw" } }, + React.createElement(window.ExcalidrawLib.Excalidraw, { + initialData: initialData, + }) + ); + }; + + ReactDOM.createRoot(document.getElementById("root")).render(React.createElement(App)); + } + + fetch("/all-libraries.excalidrawlib") + .then(function(r) { return r.json(); }) + .then(function(data) { startApp(data.libraryItems || []); }) + .catch(function(e) { + console.warn("Biblioteca não carregada:", e); + startApp(null); + }); +""" + else: + bootstrap_js = """ + var App = function() { + return React.createElement( + "div", + { style: { height: "100vh", width: "100vw" } }, + React.createElement(window.ExcalidrawLib.Excalidraw) + ); + }; + ReactDOM.createRoot(document.getElementById("root")).render(React.createElement(App)); +""" + + html = f"""<!DOCTYPE html> +<html lang="pt-BR"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Excalidraw Local</title> + <script src="/react.js"></script> + <script src="/react-dom.js"></script> + <script src="/excalidraw.js"></script> + <style> + body, html {{ margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #ffffff; }} + #root {{ width: 100vw; height: 100vh; }} + </style> +</head> +<body> + <div id="root"></div> + <script> + window.EXCALIDRAW_ASSET_PATH = "{EXCALIDRAW_ASSET_PATH}"; + + window.addEventListener("DOMContentLoaded", function() {{ + if (!window.React || !window.ReactDOM || !window.ExcalidrawLib) {{ + document.getElementById("root").innerHTML = + '<div style="padding:2rem;color:#d32f2f;font-family:sans-serif;">' + + '<strong>Falha de carga:</strong> bibliotecas não disponíveis.</div>'; + return; + }} + + {bootstrap_js} + }}); + </script> +</body> +</html> +""" + return html.encode("utf-8") + + +def shutdown_handler(signum, frame) -> None: + print("\nEncerrando servidor...") + sys.exit(0) + + +def main() -> None: + signal.signal(signal.SIGINT, shutdown_handler) + signal.signal(signal.SIGTERM, shutdown_handler) + + print(f"Cache: {CACHE_DIR}") + print("\nBaixando assets JS...") + bootstrap_assets() + + print("\nMesclando bibliotecas...") + merge_libraries() + + SPAHandler._html_payload = generate_index_html() + + server = ThreadedHTTPServer(("", PORT), SPAHandler) + print(f"\nServidor ativo em http://localhost:{PORT}") + print("Pressione Ctrl+C para encerrar.\n") + + try: + server.serve_forever() + finally: + server.server_close() + + +if __name__ == "__main__": + main() diff --git a/.local/bin/epub b/.local/bin/epub @@ -0,0 +1,42 @@ +#!/bin/sh + +[ -z "$1" ] && { printf "Uso: %s <arquivo.epub>\n" "$(basename "$0")"; exit 1; } + +FILE="$(realpath "$1")" +[ -f "$FILE" ] || { printf "Erro: arquivo não encontrado: %s\n" "$FILE"; exit 1; } + +DEST_DIR="$(dirname "$FILE")" +BASE="$(basename "$FILE" .epub)" +FINAL_NAME="${DEST_DIR}/${BASE}_editado.epub" +TEMP_DIR="/tmp/epub_${$}_$(date +%s)" + +# Extração +mkdir -p "$TEMP_DIR" +unzip -q "$FILE" -d "$TEMP_DIR" + +# Verifica se NERDTree está disponível +_has_nerdtree() { + vim -e -s \ + -c 'silent! runtime plugin/NERD_tree.vim' \ + -c 'if !exists("g:loaded_nerd_tree") | cq | endif' \ + -c 'q' >/dev/null 2>&1 +} + +if _has_nerdtree; then + (cd "$TEMP_DIR" && vim -c "NERDTree $TEMP_DIR" -c "wincmd l") +else + # netrw é built-in — funciona sem plugins + (cd "$TEMP_DIR" && "${EDITOR:-vim}" .) +fi + +# Recompactação seguindo o padrão OCF: +# mimetype deve ser o primeiro arquivo e sem compressão (-0) +printf '-> Gerando EPUB: %s\n' "$FINAL_NAME" +( + cd "$TEMP_DIR" || exit 1 + zip -q -X -0 "$FINAL_NAME" mimetype + zip -q -X -9 -r -g "$FINAL_NAME" . -x "mimetype" -x "*.DS_Store" +) + +rm -rf "$TEMP_DIR" +printf 'Concluído: %s\n' "$FINAL_NAME" diff --git a/.local/bin/install b/.local/bin/install @@ -0,0 +1,401 @@ +#!/bin/sh + +USER_NAME=$(whoami) +BASE_URL="https://codeberg.org/sivaldodavi" +SRC_DIR="$HOME/.local/src" +SHARE_DIR="$HOME/.local/share" +FONT_DIR="$SHARE_DIR/fonts" +CONF="$HOME/.config" +REPOS="st sent dwm dmenu" + +export GOPATH="$SHARE_DIR/go" +export GOBIN="$GOPATH/bin" +export PATH="$PATH:$GOBIN" + +# ─── Funções utilitárias ────────────────────────────────────────────────────── + +die() { + echo "ERRO: $1" >&2 + exit 1 +} + +mklink() { + target="$1" + link_name="$2" + mkdir -p "$(dirname "$link_name")" + rm -rf "$link_name" + ln -s "$target" "$link_name" + echo "Link: $link_name -> $target" +} + +# ─── Ajuda / Menu de comandos ───────────────────────────────────────────────── + +show_help() { + echo "Uso: $0 [comando]" + echo "" + echo "Comandos de instalação:" + echo " repos - Configura os repositórios do Alpine e atualiza" + echo " pacotes - Instala todos os pacotes base e ferramentas" + echo " permissoes - Configura grupos de usuário, doas e X11" + echo " servicos - Ativa serviços essenciais (ex: dbus)" + echo " dotfiles - Clona/Atualiza dotfiles e cria links" + echo " suckless - Clona e compila as ferramentas suckless" + echo " nsxiv - Clona e compila o nsxiv isoladamente" + echo " lf - Instala o gerenciador de arquivos lf via go" + echo " vim - Instala o gerenciador e os plugins do Vim" + echo " ffsetup - Clona e executa o ffsetup" + echo "" + echo "Comandos extras:" + echo " config - Fluxo normal: executa todos os passos de instalação acima na ordem correta" + echo " alpine - Cria o arquivo de configuração para o Alpine (answers.txt)" + echo " ssh - Configura a chave SSH com o Codeberg" + echo " fontes - Baixa e instala as fontes personalizadas" + echo " adb - Instala as ferramentas do android-tools" + echo " mail - Instala e roda o mbsync" + echo " ajuda - Mostra esta tela de ajuda" + echo "" +} + +# ─── Subcomandos extras ─────────────────────────────────────────────────────── + +install_all_fonts() { + install_font() { + name="$1"; url="$2"; dir="$FONT_DIR/$name" + mkdir -p "$dir" + echo "Baixando fonte '$name'..." + curl -L -A "Mozilla/5.0" "$url" -o "/tmp/font.zip" || { echo "⚠️ Falha ao baixar '$name', pulando." ; return; } + unzip -o "/tmp/font.zip" -d "$dir" || { echo "Falha ao extrair '$name', pulando." ; return; } + find "$dir" -type f ! -name "*.ttf" ! -name "*.otf" ! -name "*.ttc" -delete + rm "/tmp/font.zip" + echo "Fonte '$name' instalada." + } + + mkdir -p "$FONT_DIR" + install_font "FiraCode" "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/FiraCode.zip" + install_font "Bebas-Neue" "https://www.1001fonts.com/download/bebas-neue.zip" + install_font "Ruda" "https://www.1001fonts.com/download/ruda.zip" + install_font "Aptos" "https://dl.dafontfree.io/download/Microsoft-Aptos-Fonts-Family.zip" + install_font "Noto-Emoji" "https://www.1001fonts.com/download/noto-emoji.zip" + install_font "Arial" "https://font.download/dl/font/arial.zip" + install_font "Times-New-Roman" "https://font.download/dl/font/times-new-roman.zip" + + [ -x "$(command -v fc-cache)" ] && fc-cache -f +} + +setup_ssh() { + printf "Email para a chave SSH: " + read -r SSH_EMAIL + [ -z "$SSH_EMAIL" ] && die "Email não pode ser vazio." + + mkdir -p "$SHARE_DIR/ssh" + chmod 700 "$SHARE_DIR/ssh" + + KEY_PATH="$HOME/.ssh/id_ed25519" + if [ -f "$KEY_PATH" ]; then + echo "Já existe uma chave em $KEY_PATH, pulando geração." + else + ssh-keygen -t ed25519 -C "$SSH_EMAIL" -f "$KEY_PATH" -N "" || die "Falha ao gerar chave SSH." + fi + + echo "\n Chave pública (cole no Codeberg/Github em Settings → SSH/GPG Keys):" + echo "─────────────────────────────────────────" + cat "$KEY_PATH.pub" + echo "─────────────────────────────────────────" + printf "Pressione Enter após cadastrar a chave no Codeberg/Github para testar a conexão... " + read -r _ + + ssh -o StrictHostKeyChecking=accept-new -T git@codeberg.org +} + +setup_android() { + echo "Instalando android-tools..." + doas apk add android-tools || die "Falha ao instalar android-tools." + echo " android-tools instalado." +} + +setup_email() { + echo "Instalando mbsync..." + doas apk add mbsync || die "Falha ao instalar mbsync." + echo "mbsync instalado." +} + +setup_alpine_answers() { + echo "Gerando arquivo de respostas para o instalador do Alpine..." + cat << ALPINE_EOF > ./answers.txt +# Configuração do teclado +KEYMAPOPTS="br br-thinkpad" + +# Nome do host (Hostname) +HOSTNAMEOPTS=alpine + +# Gerenciador de dispositivos do sistema +DEVDOPTS=mdev + +# Configuração das interfaces de rede +INTERFACESOPTS="auto lo +iface lo inet loopback + +auto usb0 +iface usb0 inet dhcp +hostname alpine +" + +# Definição do fuso horário (Timezone) +TIMEZONEOPTS="Brazil/East" + +# Configuração de proxy de rede +PROXYOPTS=none + +# Seleção automática do espelho (mirror) mais rápido +APKREPOSOPTS="-f" + +# Criação e privilégios do usuário administrador +USEROPTS="-a -g audio,input,video,netdev -f \"Sivaldo Davi\" sivas" + +# Ativação do servidor SSH +SSHDOPTS=openssh + +# Cliente de sincronização de horário (NTP) +NTPOPTS=busybox + +# Instalação no disco com criptografia em modo sistema (sys) +DISKOPTS="-e -m sys /dev/nvme0n1" +ALPINE_EOF + echo "Arquivo 'answers.txt' gerado com sucesso para a instalação do Alpine!" +} + +# ─── Módulos principais da instalação ───────────────────────────────────────── + +setup_repos() { + echo "Configurando repositórios do Alpine..." + command -v doas >/dev/null || die "'doas' não está instalado. Instale-o com: apk add doas" + + cat <<REPOS_EOF | doas tee /etc/apk/repositories >/dev/null +http://dl-cdn.alpinelinux.org/alpine/edge/main +http://dl-cdn.alpinelinux.org/alpine/edge/community +REPOS_EOF + + doas apk update || die "Falha em 'apk update'." + doas apk upgrade --available || die "Falha em 'apk upgrade'." + echo " Repositórios atualizados." +} + +install_packages() { + echo "Instalando pacotes base..." + BUILD_DEPS="gcc g++ make git libx11-dev libxft-dev libxinerama-dev ncurses-dev imlib2-dev libexif-dev" + X11_TOOLS="brightnessctl xorg-server xinit setxkbmap xclip xdotool xsetroot xautolock unclutter-xfixes font-dejavu font-liberation xf86-input-libinput" + AUDIO="alsa-utils alsaconf pipewire pipewire-alsa pipewire-pulse wireplumber pipewire-spa-bluez" + DEV_NET="go curl openssh netcat-openbsd iproute2 firefox" + CLI_TOOLS="bash vim fastfetch fzf grep ncurses zip unzip imagemagick maim slop mpv jq python3 py3-pip ffmpeg" + PROD_SEC="aerc zathura zathura-pdf-mupdf hugo pandoc typst gnupg pass pass-otp openssl openssl-dev dbus dbus-x11" + + ALL_PACKAGES="$BUILD_DEPS $X11_TOOLS $AUDIO $DEV_NET $CLI_TOOLS $PROD_SEC" + FAILED_PKGS="" + + for pkg in $ALL_PACKAGES; do + if ! doas apk add "$pkg" 2>/dev/null; then + echo "Pacote não encontrado ou falhou: $pkg" + FAILED_PKGS="$FAILED_PKGS $pkg" + fi + done + + if [ -n "$FAILED_PKGS" ]; then + echo "\n Os seguintes pacotes falharam e foram pulados:$FAILED_PKGS" + echo " Verifique os nomes e instale manualmente se necessário.\n" + fi + + doas setup-xorg-base || echo "'setup-xorg-base' falhou, continue manualmente se necessário." + echo "Pacotes processados." +} + +setup_permissions() { + echo "Configurando permissões e X11..." + for grp in lp wheel audio video input messagebus; do + doas adduser "$USER_NAME" "$grp" || echo "Não foi possível adicionar '$USER_NAME' ao grupo '$grp'." + done + + doas mkdir -p /etc/doas.d + cat <<DOAS_EOF | doas tee /etc/doas.d/doas.conf >/dev/null +permit nopass $USER_NAME +permit nopass $USER_NAME as root cmd /sbin/poweroff +permit nopass $USER_NAME as root cmd /sbin/reboot +DOAS_EOF + + doas mkdir -p /etc/X11/xorg.conf.d + cat <<X11_EOF | doas tee /etc/X11/xorg.conf.d/10-mouse.conf >/dev/null +Section "InputClass" + Identifier "touchpad" + MatchIsTouchpad "on" + Driver "libinput" + Option "Tapping" "on" +EndSection +X11_EOF + echo "Permissões configuradas." +} + +setup_services() { + echo "Configurando serviços..." + doas rc-service dbus start || echo "Falha ao iniciar dbus." + doas rc-update add dbus default + echo "Serviços configurados." +} + +setup_dotfiles() { + echo "Configurando Dotfiles..." + if [ -d "$HOME/.git" ]; then + (cd "$HOME" && git pull) || echo "Falha ao atualizar dotfiles." + else + git clone "$BASE_URL/dotfiles.git" "$HOME/dotfiles_tmp" || die "Falha ao clonar dotfiles." + cp -af "$HOME/dotfiles_tmp/." "$HOME/" + rm -rf "$HOME/dotfiles_tmp" + fi + + mklink "$CONF/X11/profile" "$HOME/.profile" + mklink "$CONF/X11/xinitrc" "$HOME/.xinitrc" + + mkdir -p "$SHARE_DIR/gnupg" + chmod 700 "$SHARE_DIR/gnupg" + echo "✅ Dotfiles configurados." +} + +install_suckless() { + echo "Compilando ferramentas Suckless..." + mkdir -p "$SRC_DIR" + SUCKLESS_DIR="$SRC_DIR/suckless" + + if [ -d "$SUCKLESS_DIR" ]; then + echo "Git: repositório suckless já existe, atualizando..." + (cd "$SUCKLESS_DIR" && git pull) || echo "Falha ao atualizar repositório suckless." + else + git clone "$BASE_URL/suckless.git" "$SUCKLESS_DIR" || die "Falha ao clonar repositório suckless." + fi + + for repo in $REPOS; do + if [ ! -d "$SUCKLESS_DIR/$repo" ]; then + echo "Diretório '$repo' não encontrado em $SUCKLESS_DIR, pulando." + continue + fi + echo "Compilando $repo..." + (cd "$SUCKLESS_DIR/$repo" && doas make clean install) || echo "Falha ao compilar '$repo'." + done + echo "Suckless finalizado." +} + +install_nsxiv() { + echo "Clonando e compilando nsxiv..." + NSXIV_TMP="/tmp/nsxiv_build" + rm -rf "$NSXIV_TMP" + + if git clone "https://codeberg.org/nsxiv/nsxiv" "$NSXIV_TMP"; then + (cd "$NSXIV_TMP" && doas make clean install) || echo "Falha ao compilar 'nsxiv'." + rm -rf "$NSXIV_TMP" + echo "'nsxiv' instalado." + else + echo "Falha ao clonar o repositório do nsxiv." + fi +} + +install_lf() { + echo "📁 Instalando LF (file manager)..." + mkdir -p "$GOPATH" + CGO_ENABLED=0 go install -ldflags="-s -w" github.com/gokcehan/lf@latest || die "Falha ao instalar lf via go." + + if [ -f "$GOBIN/lf" ]; then + doas mv "$GOBIN/lf" /usr/local/bin/ + echo "lf instalado em /usr/local/bin/." + else + echo "Binário lf não encontrado após go install." + fi +} + +setup_vim() { + echo "Configurando plugins do Vim..." + if command -v vim >/dev/null; then + curl -fLo "$HOME/.config/vim/plugin/pm.vim" --create-dirs "https://git.sr.ht/~bpv/pm/blob/master/pm.vim" + vim +DownloadPlugins +qall || echo "Falha ao instalar plugins do Vim." + echo "Vim configurado." + else + echo "Vim não encontrado, pulando setup." + fi +} + +setup_ffsetup() { + echo "Configurando Firefox (ffsetup)..." + git clone "https://codeberg.org/sivaldodavi/ffsetup.git" "/tmp/ffsetup" || echo "Falha ao clonar ffsetup." + + if [ -d "/tmp/ffsetup" ]; then + (cd "/tmp/ffsetup" && chmod +x ffsetup.sh && ./ffsetup.sh) || echo "Falha ao executar ffsetup.sh." + rm -rf "/tmp/ffsetup" + echo "ffsetup concluído." + fi +} + +# ─── Fluxo Completo (Config) ────────────────────────────────────────────────── + +run_full_installation() { + setup_repos + install_packages + setup_permissions + setup_services + setup_dotfiles + install_suckless + install_nsxiv + install_lf + setup_vim + setup_ffsetup + + echo "" + echo "──────────────────────" + echo " Instalação concluída " + echo "──────────────────────" +} + +# ─── Pacotes ────────────────────────────────────────────────────── + +# brightnessctl: ajuste de brilho de tela +# setxkbmap: definição de layout do teclado +# xclip: gerenciador da área de transferência (clipboard) +# xdotool / xsetroot: automação e manipulação da barra de status +# xautolock: bloqueio automático de tela por inatividade +# unclutter-xfixes: oculta o cursor do mouse quando inativo +# xf86-...-libinput: driver do Xorg para funcionamento de touchpads/mouses +# go: linguagem Golang (usada para instalar o LF) +# curl / openssh: requisições web e acessos remotos seguros +# lynx /w3m/ / firefox: navegadores (um cli minimalista e um gráfico completo) +# netcat / iproute2: ferramentas essenciais de diagnóstico de rede +# fzf / jq / grep: buscadores e manipuladores de texto/arquivos JSON +# ncurses: biblioteca base para interfaces ricas no terminal +# maim / slop: ferramentas para captura de tela (screenshot) +# mpv / ffmpeg: player de vídeo leve e motor de conversão de mídia +# aerc: cliente de email para terminal +# zathura: leitor minimalista para PDFs e e-books +# sc-im: editor de planilhas para o terminal (estilo vim) +# hugo / pandoc / typst: criadores de sites estáticos, conversores e tipografia +# gnupg / pass[-otp]: criptografia de arquivos e gerenciador de senhas +# dbus / dbus-x11: barramento de comunicação inter-processos no Linux + +# ─── Controle do Roteamento de Comandos ─────────────────────────────────────── + +case "$1" in + # Módulos individuais + repos) setup_repos ;; + pacotes) install_packages ;; + permissoes) setup_permissions ;; + servicos) setup_services ;; + dotfiles) setup_dotfiles ;; + suckless) install_suckless ;; + nsxiv) install_nsxiv ;; + lf) install_lf ;; + vim) setup_vim ;; + ffsetup) setup_ffsetup ;; + + # Fluxos e extras originais + config) run_full_installation ;; + alpine) setup_alpine_answers ;; + ssh) setup_ssh ;; + fontes) install_all_fonts ;; + adb) setup_android ;; + mail) setup_email ;; + ajuda|"") show_help ;; + *) echo "Comando inválido: $1"; show_help; exit 1 ;; +esac diff --git a/.local/bin/kindle b/.local/bin/kindle @@ -0,0 +1,138 @@ +#!/bin/sh + +set -eu + +KINDLE_NET="10.0.0" +KINDLE_PORT="2222" +KINDLE_USER="root" +KINDLE_REMOTE_PATH="/" +MOUNT_POINT="${HOME}/.local/share/koreader" +SSH_KEY="${HOME}/.ssh/kindle_rsa" +LOGFILE="/tmp/kindle-mount.log" + +if [ -t 1 ]; then + R='\033[0;31m' G='\033[0;32m' Y='\033[0;33m' B='\033[0;34m' N='\033[0m' +else + R='' G='' Y='' B='' N='' +fi + +log() { printf "${B}[*]${N} %s\n" "$*" | tee -a "$LOGFILE"; } +ok() { printf "${G}[✔]${N} %s\n" "$*" | tee -a "$LOGFILE"; } +warn() { printf "${Y}[!]${N} %s\n" "$*" | tee -a "$LOGFILE"; } +die() { printf "${R}[✘]${N} %s\n" "$*" | tee -a "$LOGFILE"; exit 1; } + +MY_UID=$(id -u) +MY_GID=$(id -g) + +check_deps() { + # Mantém apenas sshfs e fuse, ignorando openssh e nc + MISSING="" + command -v sshfs >/dev/null 2>&1 || MISSING="$MISSING sshfs" + apk info -e fuse >/dev/null 2>&1 || MISSING="$MISSING fuse" + + if [ -n "$MISSING" ]; then + log "Instalando dependências:$MISSING" + doas apk add $MISSING >/dev/null 2>&1 || die "Falha ao instalar pacotes." + fi + + for cmd in sshfs fusermount3 doas; do + command -v "$cmd" >/dev/null 2>&1 || die "Dependência ausente: '$cmd'" + done +} + +is_mounted() { + grep -qs " ${MOUNT_POINT} fuse.sshfs " /proc/mounts +} + +setup_fuse() { + if ! grep -q "^user_allow_other" /etc/fuse.conf 2>/dev/null; then + log "Habilitando 'user_allow_other' em /etc/fuse.conf..." + doas sh -c "echo 'user_allow_other' >> /etc/fuse.conf" \ + || die "Falha ao editar /etc/fuse.conf." + fi + if ! lsmod | grep -q "^fuse "; then + log "Carregando módulo FUSE..." + doas modprobe fuse || die "Falha ao carregar o módulo FUSE." + fi +} + +do_umount() { + if is_mounted; then + log "Desmontando ${MOUNT_POINT}..." + fusermount3 -u "$MOUNT_POINT" \ + || doas umount -l "$MOUNT_POINT" \ + || die "Não foi possível desmontar." + ok "Kindle desmontado com sucesso." + else + warn "Kindle não está montado." + fi +} + +do_mount() { + setup_fuse + mkdir -p "$MOUNT_POINT" + + if is_mounted; then + warn "Kindle já está montado. Use 'umount' para desmontar." + exit 0 + fi + + printf "[*] Últimos dígitos do IP do Kindle (ex: 108): " + read -r LAST + KINDLE_IP="${KINDLE_NET}.${LAST}" + + log "Verificando conectividade com ${KINDLE_IP}:${KINDLE_PORT}..." + nc -z -w 3 "$KINDLE_IP" "$KINDLE_PORT" >/dev/null 2>&1 \ + || warn "Aviso: 'nc' falhou ou não existe, tentando montar mesmo assim..." + + SSH_OPTS="" + if [ -f "$SSH_KEY" ]; then + SSH_OPTS="-o IdentityFile=${SSH_KEY}" + log "Usando chave SSH: ${SSH_KEY}" + else + warn "Chave SSH não encontrada — será solicitada senha." + fi + + log "Montando Kindle (${KINDLE_IP}) em ${MOUNT_POINT}..." + + # shellcheck disable=SC2086 + sshfs "${KINDLE_USER}@${KINDLE_IP}:${KINDLE_REMOTE_PATH}" "$MOUNT_POINT" \ + -p "$KINDLE_PORT" \ + -o allow_other \ + -o uid="$MY_UID" \ + -o gid="$MY_GID" \ + -o reconnect \ + -o ServerAliveInterval=15 \ + -o ServerAliveCountMax=3 \ + -o follow_symlinks \ + -o StrictHostKeyChecking=accept-new \ + -o compression=yes \ + -o cache=yes \ + -o kernel_cache \ + $SSH_OPTS \ + || die "Falha na montagem. Consulte: ${LOGFILE}" + + ok "Kindle montado em: ${MOUNT_POINT}" +} + +do_status() { + printf "${B}[i]${N} Mount point : %s\n" "$MOUNT_POINT" + if is_mounted; then + ok "Status : MONTADO" + df -h "$MOUNT_POINT" 2>/dev/null | tail -1 | awk '{printf " Uso: %s de %s (%s livre)\n", $3, $2, $4}' + else + warn "Status : NÃO montado" + fi +} + +check_deps + +ACTION="${1:-mount}" + +case "$ACTION" in + mount) do_mount ;; + umount) do_umount ;; + status) do_status ;; + toggle) is_mounted && do_umount || do_mount ;; + *) die "Uso: $0 [mount|umount|status|toggle]" ;; +esac diff --git a/.local/bin/monitor b/.local/bin/monitor @@ -0,0 +1,42 @@ +#!/bin/sh + +INTERNAL="eDP-1" +EXTERNAL="HDMI-1" +DEFAULT_MODE="800x600" + +on() { + echo "Ligando $EXTERNAL à direita de $INTERNAL..." + xrandr --output "$EXTERNAL" --mode "$DEFAULT_MODE" --right-of "$INTERNAL" +} +off() { + echo "Desligando $EXTERNAL..." + xrandr --output "$EXTERNAL" --off +} +mirror() { + echo "Espelhando telas..." + xrandr --output "$EXTERNAL" --mode "$DEFAULT_MODE" --same-as "$INTERNAL" +} +only_external() { + echo "Usando apenas monitor externo..." + xrandr --output "$EXTERNAL" --mode "$DEFAULT_MODE" --primary --output "$INTERNAL" --off +} +status() { xrandr --listactivemonitors; } + +menu() { + choice=$(printf 'Ligar\nDesligar\nEspelhar\nApenas externo\nStatus' \ + | dmenu -i -l 5 -p "Monitor:") + case "$choice" in + Ligar) on ;; + Desligar) off ;; + Espelhar) mirror ;; + "Apenas externo") only_external ;; + Status) status ;; + esac +} + +case "${1:-menu}" in + on) on ;; + off) off ;; + menu) menu ;; + *) echo "Uso: $(basename "$0") [on|off|menu]" ;; +esac diff --git a/.local/bin/open b/.local/bin/open @@ -0,0 +1,42 @@ +#!/bin/sh + +TARGET="$1" + +# Se o arquivo nao existe, encerra +[ ! -e "$TARGET" ] && exit 1 + +# Se for diretorio, nao faz nada +[ -d "$TARGET" ] && exit 0 + +# Extensao em minusculas +EXT=$(printf '%s' "${TARGET##*.}" | tr '[:upper:]' '[:lower:]') + +case "$EXT" in + # Texto e arquivos de codigo + txt|md|conf|cfg|sh|json|yml|yaml|toml|log|csv|c|h|py|js|html|css) + "${EDITOR}" "$TARGET" ;; + + # Imagens + png|jpg|jpeg|gif|webp|bmp|svg) + nsxiv -a "$TARGET" >/dev/null 2>&1 & ;; + + # Video + mp4|mkv|webm|avi|mov) + mpv "$TARGET" >/dev/null 2>&1 & ;; + + # Audio ogg: forca modo sem video + ogg) + mpv --no-video "$TARGET" >/dev/null 2>&1 & ;; + + # Audio + mp3|flac|wav|m4a) + mpv "$TARGET" >/dev/null 2>&1 & ;; + + # Documentos + pdf|epub|djvu) + zathura "$TARGET" >/dev/null 2>&1 & ;; + + # Extensao desconhecida: tenta abrir como texto + *) + "${EDITOR}" "$TARGET" ;; +esac diff --git a/.local/bin/pass-menu b/.local/bin/pass-menu @@ -0,0 +1,10 @@ +#!/bin/bash + +# Busca as senhas, remove o caminho inicial e a extensão .gpg +entry=$(find "$PASSWORD_STORE_DIR" -name '*.gpg' 2>/dev/null | sed "s|$PASSWORD_STORE_DIR/||; s/\.gpg$//" | sort | dmenu -p "Pass:") + +# Se o usuário não selecionar nada, sai do script +[ -z "$entry" ] && exit 0 + +# Copia a senha para a área de transferência +pass -c "$entry" diff --git a/.local/bin/ptman b/.local/bin/ptman @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +import os +import sys +import json +import subprocess +import urllib.request +import urllib.error +import time +import re +from concurrent.futures import ThreadPoolExecutor + +API_URL = "https://translate.disroot.org/translate" +TARGET_DIR = os.path.expanduser("~/.local/share/man/pt") +CHUNK_LIMIT = 3000 +TRANSLATE_WORKERS = 3 # nº de chunks traduzidos em paralelo +REQUEST_DELAY = 1.0 # respiro entre disparos, por worker + + +def clean_backspaces(text): + """Remove sequências de overstrike (char + backspace) usadas para negrito/sublinhado.""" + while '\x08' in text: + text = re.sub(r'.\x08', '', text) + return text + + +def split_long_line(line, limit): + """Quebra uma única linha maior que o limite em pedaços menores.""" + return [line[i:i + limit] for i in range(0, len(line), limit)] or [line] + + +def translate_chunk(text): + """Envia um bloco de texto para a API de tradução, com retentativas.""" + payload = {"q": text, "source": "auto", "target": "pt", "api_key": ""} + req = urllib.request.Request( + API_URL, data=json.dumps(payload).encode('utf-8'), + headers={'Content-Type': 'application/json'}, method='POST' + ) + for attempt in range(5): + try: + with urllib.request.urlopen(req, timeout=30) as response: + res_data = json.loads(response.read().decode('utf-8')) + return res_data.get("translatedText", "") + except urllib.error.HTTPError as e: + if e.code == 429: + time.sleep(3 * (attempt + 1)) + continue + return None # erro definitivo do servidor + except Exception: + time.sleep(2) + continue + return None + + +def build_chunks(text): + """Divide o texto em blocos respeitando CHUNK_LIMIT.""" + lines = text.splitlines() + chunks, current_chunk, current_length = [], [], 0 + + for line in lines: + if len(line) > CHUNK_LIMIT: + if current_chunk: + chunks.append("\n".join(current_chunk)) + current_chunk, current_length = [], 0 + chunks.extend(split_long_line(line, CHUNK_LIMIT)) + continue + + if current_length + len(line) + 1 > CHUNK_LIMIT: + chunks.append("\n".join(current_chunk)) + current_chunk, current_length = [line], len(line) + else: + current_chunk.append(line) + current_length += len(line) + 1 + + if current_chunk: + chunks.append("\n".join(current_chunk)) + + return chunks + + +def translate_text(text, status_prefix=""): + """Traduz um texto inteiro, fatiando em chunks e mostrando progresso em paralelo.""" + chunks = build_chunks(text) + total_chunks = len([c for c in chunks if c.strip()]) + if total_chunks == 0: + return text + + results = [None] * len(chunks) + done_count = [0] + + def worker(idx_chunk): + idx, chunk = idx_chunk + if not chunk.strip(): + results[idx] = chunk + return + translated = translate_chunk(chunk) + results[idx] = translated if translated is not None else chunk + done_count[0] += 1 + if status_prefix: + pct = int((done_count[0] / total_chunks) * 100) + print( + f"\r{status_prefix} -> Traduzindo... {done_count[0]}/{total_chunks} partes ({pct}%)", + end="", flush=True + ) + time.sleep(REQUEST_DELAY) + + with ThreadPoolExecutor(max_workers=TRANSLATE_WORKERS) as pool: + list(pool.map(worker, enumerate(chunks))) + + if status_prefix: + print(f"\r{status_prefix} -> Concluído!{' ' * 25}", flush=True) + + return "\n".join(results) + + +def resolve_man_path(args): + """Resolve o caminho físico do manual via `man -w`.""" + try: + output = subprocess.check_output( + ["man", "-w"] + args, stderr=subprocess.DEVNULL + ).decode('utf-8').strip() + except subprocess.CalledProcessError: + return None + + candidates = [p for p in output.splitlines() if p.strip()] + return candidates[0] if candidates else None + + +def process_manual(args): + """Busca, traduz (se necessário) e exibe o manual especificado.""" + src_path = resolve_man_path(args) + if not src_path: + print(f"Erro: Manual não encontrado para: {' '.join(args)}") + return False + + parts = src_path.split('/') + if len(parts) < 2: + print("Erro: Caminho do manual inválido.") + return False + + section_dir, filename = parts[-2], parts[-1] + clean_filename = filename.replace('.gz', '') + dest_path = os.path.join(TARGET_DIR, section_dir, f"{clean_filename}.txt") + + # Retorna a versão em cache, se existir + if os.path.exists(dest_path): + subprocess.run(["vim", dest_path]) + return True + + print(f"-> Origem localizada: {src_path}") + package_origin = "Desconhecido" + + try: + apk_out = subprocess.check_output( + ["apk", "info", "-W", src_path], stderr=subprocess.DEVNULL + ).decode('utf-8').strip() + if "is owned by" in apk_out: + package_origin = apk_out.split("is owned by")[-1].strip() + except Exception: + pass + + print(f"-> Pacote: [{package_origin}]") + print("-> Traduzindo sob demanda...") + + try: + raw_text = subprocess.check_output( + ["mandoc", "-Tutf8", src_path], stderr=subprocess.DEVNULL + ).decode('utf-8', errors='ignore') + raw_text = clean_backspaces(raw_text) + + if not raw_text.strip(): + return False + + translated = translate_text(raw_text, f"-> {clean_filename}") + + os.makedirs(os.path.dirname(dest_path), exist_ok=True) + with open(dest_path, "w", encoding="utf-8") as f: + f.write(translated) + + subprocess.run(["vim", dest_path]) + return True + + except Exception as e: + print(f"\nFalha na tradução: {e}") + print("Abrindo fallback em inglês...\n") + time.sleep(1.5) + subprocess.run(["man"] + args) + return False + + +def main(): + args = sys.argv[1:] + + if not args: + print("Uso:") + print(" ptman <cmd> (Ex: ptman git)") + print(" ptman <sec> <cmd> (Ex: ptman 5 crontab)") + print(" ptman clean (Apaga o cache atual)") + sys.exit(1) + + cmd = args[0].lower() + + if cmd == "clean": + if os.path.exists(TARGET_DIR): + subprocess.run(["rm", "-rf", TARGET_DIR]) + print("Cache limpo.") + else: + print("Cache já estava vazio.") + sys.exit(0) + + process_manual(args) + + +if __name__ == "__main__": + main() diff --git a/.local/bin/recorder b/.local/bin/recorder @@ -0,0 +1,71 @@ +#!/bin/sh + +NAME="${1:-rec_$(date +%Y%m%d_%H%M%S)}" +OUT="${NAME%.mkv}.mkv" + +cleanup() { + printf "\nEncerrando...\n" + wpctl set-mute @DEFAULT_AUDIO_SINK@ 0 + kill "$SCRCPY_PID" 2>/dev/null + wait "$SCRCPY_PID" 2>/dev/null + printf "Arquivo salvo com sucesso: %s\n" "$OUT" +} + +trap cleanup EXIT INT TERM + +# Verifica/Instala apenas o scrcpy +if ! command -v scrcpy >/dev/null 2>&1; then + printf "📦 'scrcpy' não encontrado. Instalando...\n" + doas apk add scrcpy || { printf "ERRO: Falha ao instalar scrcpy.\n" >&2; exit 1; } +fi + +if ! adb devices | grep -q "device$"; then + printf "ERRO: Nenhum celular detectado via ADB.\n" + exit 1 +fi + +# Detecta resolução da tela principal automaticamente +RESOLUTION=$(xrandr 2>/dev/null \ + | awk '/\*/ {print $1; exit}') + +if [ -z "$RESOLUTION" ]; then + printf "Aviso: resolução não detectada, usando 1366x768.\n" >&2 + RESOLUTION="1366x768" +fi + +printf "Resolução detectada: %s\n" "$RESOLUTION" +printf "Iniciando microfone do celular...\n" + +scrcpy --no-video --no-control \ + --audio-codec=opus \ + --audio-buffer=200 \ + --audio-source=voice-performance \ + > /tmp/scrcpy.log 2>&1 & + +SCRCPY_PID=$! +sleep 3 + +if ! kill -0 "$SCRCPY_PID" 2>/dev/null; then + printf "ERRO: Falha na inicialização do scrcpy. Log:\n" >&2 + cat /tmp/scrcpy.log >&2 + exit 1 +fi + +wpctl set-mute @DEFAULT_AUDIO_SINK@ 1 +printf "Alto-falante mutado (proteção contra microfonia ativada).\n" + +AUDIO_SINK=$(pactl get-default-sink 2>/dev/null) +SINK_MONITOR="${AUDIO_SINK}.monitor" + +printf "Gravando (%s a 30fps) e áudio (%s)...\n" "$RESOLUTION" "$SINK_MONITOR" +printf "Pressione 'q' para parar a gravação.\n\n" + +ffmpeg -hide_banner \ + -f x11grab -video_size "$RESOLUTION" -framerate 30 -i :0.0 \ + -f pulse -fragment_size 4096 \ + -i "$SINK_MONITOR" \ + -c:v libx264 -preset ultrafast -crf 23 \ + -c:a aac -b:a 192k \ + -af "aresample=async=1000" \ + -map 0:v:0 -map 1:a:0 \ + "$OUT" diff --git a/.local/bin/share b/.local/bin/share @@ -0,0 +1,102 @@ +#!/bin/sh + +PORT=8000 + +# Detecta o IP da interface de saída padrão +get_ip() { + # Tenta a rota de saída; fallback para o primeiro IP da máquina + iface=$(ip route get 1.1.1.1 2>/dev/null | awk '{print $5; exit}') + if [ -z "$iface" ]; then + # Fallback para redes sem conectividade externa (isoladas) + hostname -I 2>/dev/null | awk '{print $1; exit}' + return + fi + ip -4 addr show "$iface" 2>/dev/null \ + | awk '/inet / {split($2, a, "/"); print a[1]; exit}' +} + +# Exibe QR Code se qrencode estiver disponível +show_qr() { + if command -v qrencode >/dev/null 2>&1; then + printf '%s' "$1" | qrencode -t ansiutf8 + fi + printf 'URL: %s\n' "$1" +} + +# Inicia o servidor HTTP (download ou upload) +start_server() { + local mode="$1" + + if [ "$mode" = "receive" ]; then + if python3 -m uploadserver --help >/dev/null 2>&1; then + python3 -m uploadserver "$PORT" + else + VENV_DIR="/tmp/share_upload_venv" + if [ ! -d "$VENV_DIR" ]; then + printf 'Criando venv temporário para uploadserver...\n' + python3 -m venv "$VENV_DIR" + "$VENV_DIR/bin/pip" install --quiet uploadserver + fi + printf 'Iniciando servidor de upload...\n' + "$VENV_DIR/bin/python3" -m uploadserver "$PORT" + fi + else + python3 -m http.server "$PORT" + fi +} + +share() { + ip=$(get_ip) || { echo "Erro: IP não detectado." >&2; exit 1; } + [ -z "$ip" ] && { echo "Erro: IP não detectado." >&2; exit 1; } + show_qr "http://$ip:$PORT" + printf 'Servindo pasta atual em: http://%s:%s\n' "$ip" "$PORT" + start_server share +} + +receive() { + ip=$(get_ip) || { echo "Erro: IP não detectado." >&2; exit 1; } + [ -z "$ip" ] && { echo "Erro: IP não detectado." >&2; exit 1; } + show_qr "http://$ip:$PORT/upload" + printf 'Aguardando uploads em: http://%s:%s/upload\n' "$ip" "$PORT" + start_server receive +} + +send() { + [ -z "$1" ] || [ -z "$2" ] && { + echo "Uso: $(basename "$0") send <origem> <destino>" >&2 + exit 1 + } + rsync -avz --progress "$1" "$2" +} + +sharefile() { + [ -z "$1" ] || [ ! -f "$1" ] && { + echo "Erro: arquivo não encontrado." >&2 + exit 1 + } + + filepath=$(realpath "$1") + filename=$(basename "$filepath") + + # Cria diretório temporário com link simbólico para servir apenas o arquivo + tmpdir=$(mktemp -d) || exit 1 + ln -s "$filepath" "$tmpdir/$filename" + + ip=$(get_ip) || { echo "Erro: IP não detectado." >&2; rm -rf "$tmpdir"; exit 1; } + show_qr "http://$ip:$PORT/$filename" + printf 'Servindo arquivo: %s\n' "$filename" + + (cd "$tmpdir" && start_server share) + rm -rf "$tmpdir" +} + +case "${1:-}" in + share) share ;; + receive) receive ;; + send) send "$2" "$3" ;; + file) sharefile "$2" ;; + *) + printf 'Uso: %s {share|receive|send|file} [args...]\n' "$(basename "$0")" + exit 1 + ;; +esac diff --git a/.local/bin/statusbar b/.local/bin/statusbar @@ -0,0 +1,91 @@ +#!/bin/sh +# Cada módulo tem seu próprio intervalo. O loop central acorda a cada 1s e só chama xsetroot quando algo mudou. + +BAT_INT=30 +VOL_INT=2 +NET_INT=30 + +last_bat=0; last_vol=0; last_net=0 +s_bat=""; s_vol=""; s_net="" +old_status="" + +while true; do + now=$(date +%s) + changed=0 + + # ── BATERIA ─────────────────────────────────────────────────────────────── + if [ $(( now - last_bat )) -ge $BAT_INT ]; then + if [ -d /sys/class/power_supply/BAT0 ]; then + cap=$(cat /sys/class/power_supply/BAT0/capacity 2>/dev/null) + status_bat=$(cat /sys/class/power_supply/BAT0/status 2>/dev/null) + + case "$status_bat" in + Charging) icon="⚡" ;; + *) + [ "${cap:-100}" -le 20 ] && icon="🪫" || icon="🔋" + ;; + esac + + if [ "${cap:-100}" -lt 50 ] || [ "$status_bat" = "Charging" ]; then + new_bat="$icon $cap%" + else + new_bat="" + fi + else + new_bat="" + fi + + [ "$new_bat" != "$s_bat" ] && s_bat="$new_bat" && changed=1 + last_bat=$now + fi + + # ── VOLUME ──────────────────────────────────────────────────────────────── + if [ $(( now - last_vol )) -ge $VOL_INT ]; then + vol_raw=$(pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null \ + | grep -oE '[0-9]+%' | head -n 1 | tr -d '%') + muted=$(pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null \ + | grep -c 'yes') + + if [ "$muted" -gt 0 ] || [ "${vol_raw:-0}" -eq 0 ]; then + new_vol="🔇 mute" + else + [ "${vol_raw:-0}" -gt 60 ] && v_icon="🔊" || v_icon="🔉" + new_vol="$v_icon $vol_raw%" + fi + + [ "$new_vol" != "$s_vol" ] && s_vol="$new_vol" && changed=1 + last_vol=$now + fi + + # ── REDE ────────────────────────────────────────────────────────────────── + if [ $(( now - last_net )) -ge $NET_INT ]; then + new_net="off" + for iface in eth0 wlan0 usb0; do + state_file="/sys/class/net/$iface/operstate" + if [ -f "$state_file" ]; then + state=$(cat "$state_file" 2>/dev/null) + [ "$state" = "up" ] && new_net="" && break + fi + done + + [ "$new_net" != "$s_net" ] && s_net="$new_net" && changed=1 + last_net=$now + fi + + # ── ATUALIZA O XSETROOT ─────────────────────────────────────────────────── + if [ "$changed" -eq 1 ]; then + status="" + for seg in "$s_bat" "$s_vol" "$s_net"; do + [ -n "$seg" ] || continue + [ -n "$status" ] && status="$status | " + status="$status$seg" + done + + if [ "$status" != "$old_status" ]; then + xsetroot -name "$status" + old_status="$status" + fi + fi + + sleep 1 +done diff --git a/.local/bin/sys b/.local/bin/sys @@ -0,0 +1,9 @@ +#!/bin/sh + +case "$(printf "Shutdown\nReboot\nLock\nExit dwm\n" | dmenu -i -p "System:")" in + "Shutdown") doas poweroff ;; + "Reboot") doas reboot ;; + "Lock") slock ;; + "Exit dwm") pkill dwm ;; + *) exit 1 ;; +esac diff --git a/.local/bin/trans b/.local/bin/trans @@ -0,0 +1,177 @@ +#!/bin/sh + +# ─── Configuração ───────────────────────────────────────────────────────────── +API_URL="https://translate.disroot.org" +API_KEY="" +DEFAULT_SOURCE="auto" +DEFAULT_TARGET="pt" +CONFIG_FILE="$HOME/.config/translate/config" +mkdir -p "$(dirname "$CONFIG_FILE")" +# ────────────────────────────────────────────────────────────────────────────── + +usage() { + cat <<EOF +USO: + translate [opções] "texto" ["texto2" ...] + translate [opções] -f arquivo.txt + echo "texto" | translate [opções] + +OPÇÕES: + -s LANG Idioma de origem (padrão: auto) + -t LANG Idioma de destino (padrão: $DEFAULT_TARGET) + -f ARQUIVO Traduz o conteúdo de um arquivo + -o ARQUIVO Salva resultado neste arquivo (só com -f por padrão é automático) + -l Lista os idiomas disponíveis + -k KEY API key para esta execução + -v Verbose: mostra idioma detectado + -h Esta ajuda + +ARQUIVO: + Com -f, o resultado é salvo automaticamente como: + arquivo.LANG.txt (ex: doc.txt → doc.pt.txt) + Use -o para escolher outro nome de saída. + +EXEMPLOS: + translate "Hello world" + translate "Good morning" "See you later" + translate -t en "Olá mundo" + translate -f relatorio.txt # salva relatorio.pt.txt + translate -f doc.txt -t en # salva doc.en.txt + translate -f doc.txt -o saida.txt # salva no nome escolhido + translate -v "Hello" # mostra idioma detectado + translate -l # lista idiomas + +CONFIGURAÇÃO (~/.config/translate/config): + API_KEY=sua_chave + DEFAULT_TARGET=en +EOF +} + +# ─── Carrega config ─────────────────────────────────────────────────────────── +[ -f "$CONFIG_FILE" ] && . "$CONFIG_FILE" + +# ─── Parse de argumentos ────────────────────────────────────────────────────── +SOURCE="$DEFAULT_SOURCE" +TARGET="$DEFAULT_TARGET" +INPUT_FILE="" +OUTPUT_FILE="" +LIST_LANGS=0 +VERBOSE=0 + +while getopts "s:t:f:o:k:lvh" opt; do + case "$opt" in + s) SOURCE="$OPTARG" ;; + t) TARGET="$OPTARG" ;; + f) INPUT_FILE="$OPTARG" ;; + o) OUTPUT_FILE="$OPTARG" ;; + k) API_KEY="$OPTARG" ;; + l) LIST_LANGS=1 ;; + v) VERBOSE=1 ;; + h) usage; exit 0 ;; + *) usage; exit 1 ;; + esac +done +shift $((OPTIND - 1)) + +# ─── Verifica dependências ──────────────────────────────────────────────────── +for cmd in curl jq; do + command -v "$cmd" > /dev/null 2>&1 || { + printf 'Erro: "%s" não encontrado. Instale com: apk add %s\n' "$cmd" "$cmd" >&2 + exit 1 + } +done + +# ─── Lista idiomas ──────────────────────────────────────────────────────────── +if [ "$LIST_LANGS" -eq 1 ]; then + printf 'Buscando idiomas em %s...\n' "$API_URL" >&2 + response=$(curl -sf "$API_URL/languages") + [ $? -ne 0 ] || [ -z "$response" ] && { echo "Erro: não foi possível contatar $API_URL" >&2; exit 1; } + printf '\nCódigo Idioma\n────── ──────────────────────\n' + echo "$response" | jq -r '.[] | "\(.code)\t\(.name)"' | sort | \ + awk -F'\t' '{printf " %-6s %s\n", $1, $2}' + exit 0 +fi + +# ─── Função: traduz um bloco de texto, imprime resultado ───────────────────── +translate_text() { + _text="$1" + _payload=$(jq -n \ + --arg q "$_text" \ + --arg source "$SOURCE" \ + --arg target "$TARGET" \ + --arg key "$API_KEY" \ + '{q: $q, source: $source, target: $target, format: "html", api_key: $key}') + + _response=$(curl -sf \ + -X POST "$API_URL/translate" \ + -H "Content-Type: application/json" \ + -d "$_payload") + + [ $? -ne 0 ] || [ -z "$_response" ] && { echo "Erro: falha na conexão com $API_URL" >&2; return 1; } + + if echo "$_response" | jq -e '.error' > /dev/null 2>&1; then + printf 'Erro da API: %s\n' "$(echo "$_response" | jq -r '.error')" >&2 + return 1 + fi + + _translated=$(echo "$_response" | jq -r '.translatedText') + + if [ "$VERBOSE" -eq 1 ]; then + _detected=$(echo "$_response" | jq -r '.detectedLanguage.language // empty') + _conf=$(echo "$_response" | jq -r '.detectedLanguage.confidence // empty') + if [ -n "$_detected" ]; then + _pct=$(awk "BEGIN{printf \"%.0f\", $_conf * 100}" 2>/dev/null || echo "?") + printf '[detectado: %s ~%s%%] ' "$_detected" "$_pct" >&2 + fi + fi + + printf '%s\n' "$_translated" +} + +# ─── Função: deriva nome do arquivo de saída ────────────────────────────────── +derive_output() { + _in="$1" + _base="${_in%.*}" + _ext="${_in##*.}" + + if [ "$_base" = "$_in" ]; then + printf '%s.%s' "$_in" "$TARGET" + else + printf '%s.%s.%s' "$_base" "$TARGET" "$_ext" + fi +} + +# ─── Modo arquivo ───────────────────────────────────────────────────────────── +if [ -n "$INPUT_FILE" ]; then + [ -f "$INPUT_FILE" ] || { echo "Erro: arquivo '$INPUT_FILE' não encontrado." >&2; exit 1; } + + if [ -z "$OUTPUT_FILE" ]; then + OUTPUT_FILE=$(derive_output "$INPUT_FILE") + fi + + if [ -f "$OUTPUT_FILE" ]; then + printf 'Aviso: "%s" já existe e será sobrescrito.\n' "$OUTPUT_FILE" >&2 + fi + + printf 'Traduzindo "%s" (%s → %s)...\n' "$INPUT_FILE" "$SOURCE" "$TARGET" >&2 + + result=$(translate_text "$(cat "$INPUT_FILE")") || exit 1 + printf '%s\n' "$result" > "$OUTPUT_FILE" + printf 'Salvo em: %s\n' "$OUTPUT_FILE" >&2 + exit 0 +fi + +# ─── Modo texto / stdin ─────────────────────────────────────────────────────── +if [ $# -gt 0 ]; then + i=1 + for arg in "$@"; do + [ $# -gt 1 ] && printf '[%d] ' "$i" >&2 + translate_text "$arg" || exit 1 + i=$((i + 1)) + done +elif [ ! -t 0 ]; then + translate_text "$(cat)" +else + echo "Erro: nenhum texto fornecido. Use -h para ajuda." >&2 + exit 1 +fi diff --git a/.local/bin/yt b/.local/bin/yt @@ -0,0 +1,54 @@ +#!/bin/sh + +VIDS="${XDG_VIDEOS_DIR:-$HOME/videos}" +MUSC="${XDG_MUSIC_DIR:-$HOME/music}" +PICS="${XDG_PICTURES_DIR:-$HOME/pictures}" + + +# Verificação e instalação de yt-dlp +if ! command -v xrandr >/dev/null 2>&1; then + echo "📦 'yt-dlp' não encontrado. Instalando..." >&2 + doas apk add yt-dlp || { echo "ERRO: Falha ao instalar yt-dlp." >&2; exit 1; } +fi + +_act() { + _url="$1" + _action="$(printf 'Assistir (720p)\nAssistir (melhor)\nOuvir (stream)\nBaixar vídeo\nBaixar áudio (mp3)\nBaixar thumbnail' \ + | dmenu -p 'Ação:')" + [ -z "$_action" ] && return + case "$_action" in + 'Assistir (720p)') mpv --ytdl-format='bestvideo[height<=720]+bestaudio' "$_url" ;; + 'Assistir (melhor)') mpv "$_url" ;; + 'Ouvir (stream)') mpv --no-video "$_url" ;; + 'Baixar vídeo') yt-dlp -f 'bestvideo[ext=mp4]+bestaudio/best' -o "$VIDS/%(title)s.%(ext)s" "$_url" ;; + 'Baixar áudio (mp3)') yt-dlp -x --audio-format mp3 -o "$MUSC/%(title)s.%(ext)s" "$_url" ;; + 'Baixar thumbnail') yt-dlp --skip-download --write-thumbnail -o "$PICS/%(title)s.%(ext)s" "$_url" ;; + esac +} + +_search() { + _query="$1" + # busca os 10 primeiros resultados: "Título\tURL" + _results="$(yt-dlp "ytsearch10:${_query}" \ + --get-title --get-url --flat-playlist 2>/dev/null \ + | paste - -)" + [ -z "$_results" ] && { printf 'Sem resultados.\n' >&2; exit 1; } + + _choice="$(printf '%s\n' "$_results" | cut -f1 | dmenu -l 10 -p "\"${_query}\":")" + [ -z "$_choice" ] && exit 0 + + _url="$(printf '%s\n' "$_results" | awk -F'\t' -v t="$_choice" '$1==t{print $2; exit}')" + _act "$_url" +} + +# — roteamento — +if [ -n "$1" ]; then + case "$1" in + http://*|https://*) _act "$1" ;; # URL direta + *) _search "$*" ;; # termo passado como argumento + esac +else + _query="$(printf '' | dmenu -p 'Buscar no YouTube:')" + [ -z "$_query" ] && exit 0 + _search "$_query" +fi diff --git a/README.md b/README.md @@ -0,0 +1,30 @@ + +## Scripts + +**Compartilhamento:** +- `backup`: sincroniza documentos, músicas, fotos, vídeos e agenda com a nuvem [Disroot](https://disroot.org) via rclone/WebDAV — `push` envia, `pull` baixa +- `share`: transfere arquivos na rede local — serve pasta (`share`), recebe uploads (`receive`), transfere via rsync (`send`) ou serve arquivo único (`file`) + +**Mídia:** +- `yt`: busca e reproduz/baixa vídeos do YouTube via yt-dlp e mpv, com menu dmenu para escolher ação +- `recorder`: grava tela + áudio do microfone do celular via ffmpeg e scrcpy + +**Produtividade:** +- `clip`: histórico de clipboard — daemon que monitora e picker via dmenu +- `pass-menu`: seleciona entrada do password-store via dmenu e copia para clipboard +- `trans`: traduz texto, stdin ou arquivos via LibreTranslate autodeploy (disroot.org) + +**Sistema:** +- `sys`: menu de energia via dmenu — desligar, reiniciar, travar (`slock`) ou sair do dwm +- `monitor`: gerencia monitor externo HDMI — ligar, desligar, espelhar ou usar só o externo +- `statusbar`: barra de status para dwm/xsetroot — exibe bateria, volume e rede com atualização por intervalo +- `bt`: gerencia bluetooth — escaneia dispositivos, emparelha e conecta via fzf + +**Leitura e estudo:** +- `culto`: exibição de slides para culto — textos bíblicos, músicas do hinário e avisos via `sent`; suporta modo responsivo (DIRIGENTE/IGREJA) +- `kindle`: monta o sistema de arquivos do Kindle/KOReader via SSHFS para gerenciar livros +- `epub`: extrai, edita e recompacta arquivos EPUB no editor de texto +- `draw`: servidor local do Excalidraw com assets em cache e merge automático de bibliotecas + +**Instalação:** +- `install`: script único com setup completo do sistema — instala pacotes, compila ferramentas suckless (dwm, st, dmenu, sent, nsxiv), configura dotfiles, fontes, serviços, permissões e plugins do Vim; subcomando `ssh` gera e cadastra chave no Codeberg e com answerfile para `setup-alpine` — automatiza instalação do Alpine com teclado ABNT2, fuso de Brasília, criptografia de disco e criação de usuário