Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
6e050e98d3
7 files changed
+572
No files matched your search
@@ -0,0 +1,52 @@
|
||||
name: Pacote
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ["v*"]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
pacote:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Verificar código
|
||||
run: |
|
||||
python3 -m py_compile copiado.py
|
||||
shellcheck instalar.sh desinstalar.sh empacotamento/gerar-deb.sh
|
||||
|
||||
- name: Definir versão
|
||||
run: |
|
||||
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
|
||||
echo "VERSAO=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "VERSAO=0.0.0~dev${GITHUB_RUN_NUMBER}" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Gerar .deb
|
||||
run: empacotamento/gerar-deb.sh "$VERSAO"
|
||||
|
||||
- name: Testar instalação do pacote
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y ./dist/copiado_all.deb
|
||||
test -x /usr/bin/copiado
|
||||
test -f /etc/xdg/autostart/copiado.desktop
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: copiado-${{ env.VERSAO }}
|
||||
path: dist/copiado_all.deb
|
||||
|
||||
- name: Publicar release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh release create "$GITHUB_REF_NAME" dist/copiado_all.deb \
|
||||
--title "Copiado $VERSAO" --generate-notes
|
||||
@@ -0,0 +1,2 @@
|
||||
dist/
|
||||
__pycache__/
|
||||
@@ -0,0 +1,54 @@
|
||||
# Copiado
|
||||
|
||||
O histórico da área de transferência do Windows, no Linux Mint.
|
||||
Aperte **Win+V** e ele aparece, no visual do Windows 11.
|
||||
Clique em um item e ele é colado na hora.
|
||||
|
||||
## Instalar
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/lucasrguerra/copiado/main/instalar.sh | bash
|
||||
```
|
||||
|
||||
Baixa o pacote `.deb` mais recente das [Releases](https://github.com/lucasrguerra/copiado/releases),
|
||||
instala, cria o atalho Win+V e já deixa rodando. Pede sua senha (sudo).
|
||||
Para **atualizar**, rode o mesmo comando de novo.
|
||||
|
||||
## Usar
|
||||
|
||||
| Ação | Como |
|
||||
|---|---|
|
||||
| Abrir / fechar | **Win+V** |
|
||||
| Colar um item | clique, ou setas + **Enter** |
|
||||
| Fixar (não some ao limpar) | ícone de alfinete |
|
||||
| Apagar um item | ícone da lixeira ou tecla **Delete** |
|
||||
| Apagar tudo (menos os fixados) | **Limpar tudo** |
|
||||
| Fechar sem colar | **Esc** ou clicar fora |
|
||||
|
||||
Guarda os 25 últimos itens (texto e imagens) e continua lá depois de reiniciar o PC.
|
||||
|
||||
## Desinstalar
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/lucasrguerra/copiado/main/desinstalar.sh | bash
|
||||
```
|
||||
|
||||
## Se algo der errado
|
||||
|
||||
- **Win+V não abre nada:** faça o Cinnamon recarregar os atalhos:
|
||||
```bash
|
||||
gsettings set org.cinnamon.desktop.keybindings custom-list "[]"; gsettings set org.cinnamon.desktop.keybindings custom-list "['custom0']"
|
||||
```
|
||||
Se ainda assim não abrir, veja se outro atalho usa Win+V em
|
||||
*Configurações do Sistema → Teclado → Atalhos*.
|
||||
- **Abre, mas o histórico está vazio:** o app pode não estar rodando. Rode
|
||||
`copiado &`
|
||||
|
||||
## Publicar uma versão nova
|
||||
|
||||
```bash
|
||||
git tag v1.0.1 && git push origin v1.0.1
|
||||
```
|
||||
|
||||
O GitHub Actions gera o `copiado_all.deb`, testa a instalação e publica a release.
|
||||
Para gerar o pacote localmente: `empacotamento/gerar-deb.sh 1.0.1` (sai em `dist/`).
|
||||
Executable
+353
@@ -0,0 +1,353 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Copiado: histórico da área de transferência no estilo Windows 11 (Win+V).
|
||||
|
||||
Uso:
|
||||
copiado.py inicia em segundo plano (vigia o Ctrl+C)
|
||||
copiado.py show abre a janelinha do histórico
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import gi
|
||||
gi.require_version("Gtk", "3.0")
|
||||
gi.require_version("Gdk", "3.0")
|
||||
gi.require_version("GdkX11", "3.0")
|
||||
from gi.repository import Gdk, GdkPixbuf, GdkX11, Gio, GLib, Gtk, Pango # noqa: E402
|
||||
|
||||
MAX_ITENS = 25
|
||||
LARGURA, ALTURA = 380, 480
|
||||
PASTA = os.path.join(GLib.get_user_data_dir(), "copiado")
|
||||
ARQ_HIST = os.path.join(PASTA, "historico.json")
|
||||
|
||||
CSS = b"""
|
||||
#fundo {
|
||||
background-color: rgba(32, 32, 32, 0.97);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 12px;
|
||||
}
|
||||
#titulo { color: #ffffff; font-size: 15px; font-weight: 600; }
|
||||
#limpar {
|
||||
color: #9fc9ff; background: transparent; border: none; box-shadow: none;
|
||||
font-size: 12px; padding: 4px 8px; border-radius: 6px;
|
||||
}
|
||||
#limpar:hover { background-color: rgba(255, 255, 255, 0.06); }
|
||||
list { background: transparent; }
|
||||
row { background: transparent; padding: 0; outline: none; }
|
||||
row:focus, row:hover, row:selected { background: transparent; }
|
||||
.cartao {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
margin: 3px 0;
|
||||
}
|
||||
row:hover .cartao { background-color: rgba(255, 255, 255, 0.09); }
|
||||
row:focus .cartao, row:selected .cartao {
|
||||
background-color: rgba(255, 255, 255, 0.09);
|
||||
border-color: #4cc2ff;
|
||||
}
|
||||
.texto { color: #f3f3f3; font-size: 13px; }
|
||||
.icone {
|
||||
background: transparent; border: none; box-shadow: none;
|
||||
color: #a0a0a0; padding: 2px 4px; min-width: 0; min-height: 0; border-radius: 4px;
|
||||
}
|
||||
.icone:hover { background-color: rgba(255, 255, 255, 0.10); color: #ffffff; }
|
||||
.fixado, .fixado:hover { color: #4cc2ff; }
|
||||
#vazio { color: #a0a0a0; font-size: 13px; }
|
||||
scrollbar { background: transparent; border: none; }
|
||||
scrollbar slider { background-color: rgba(255,255,255,0.25); border-radius: 4px; min-width: 4px; }
|
||||
"""
|
||||
|
||||
|
||||
class Historico:
|
||||
def __init__(self):
|
||||
os.makedirs(PASTA, exist_ok=True)
|
||||
self.itens = [] # {"tipo": "texto"|"imagem", "dado": str, "fixo": bool}
|
||||
try:
|
||||
with open(ARQ_HIST) as f:
|
||||
self.itens = json.load(f)
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
|
||||
def salvar(self):
|
||||
with open(ARQ_HIST + ".tmp", "w") as f:
|
||||
json.dump(self.itens, f, ensure_ascii=False)
|
||||
os.replace(ARQ_HIST + ".tmp", ARQ_HIST)
|
||||
usadas = {i["dado"] for i in self.itens if i["tipo"] == "imagem"}
|
||||
for nome in os.listdir(PASTA):
|
||||
caminho = os.path.join(PASTA, nome)
|
||||
if nome.endswith(".png") and caminho not in usadas:
|
||||
os.remove(caminho)
|
||||
|
||||
def adicionar(self, tipo, dado):
|
||||
antigo = next((i for i in self.itens if i["tipo"] == tipo and i["dado"] == dado), None)
|
||||
fixo = antigo["fixo"] if antigo else False
|
||||
if antigo:
|
||||
self.itens.remove(antigo)
|
||||
self.itens.insert(0, {"tipo": tipo, "dado": dado, "fixo": fixo})
|
||||
soltos = [i for i in self.itens if not i["fixo"]]
|
||||
for extra in soltos[MAX_ITENS:]:
|
||||
self.itens.remove(extra)
|
||||
self.salvar()
|
||||
|
||||
def remover(self, item):
|
||||
self.itens.remove(item)
|
||||
self.salvar()
|
||||
|
||||
def limpar(self):
|
||||
self.itens = [i for i in self.itens if i["fixo"]]
|
||||
self.salvar()
|
||||
|
||||
|
||||
class App(Gtk.Application):
|
||||
def __init__(self):
|
||||
super().__init__(application_id="io.github.copiado",
|
||||
flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
|
||||
self.hist = None
|
||||
self.janela = None
|
||||
self.ignorar = None # conteúdo que nós mesmos colocamos
|
||||
|
||||
# ---------- ciclo de vida ----------
|
||||
def do_startup(self):
|
||||
Gtk.Application.do_startup(self)
|
||||
self.hold()
|
||||
self.hist = Historico()
|
||||
prov = Gtk.CssProvider()
|
||||
prov.load_from_data(CSS)
|
||||
Gtk.StyleContext.add_provider_for_screen(
|
||||
Gdk.Screen.get_default(), prov, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||
self.clip = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
||||
self.clip.connect("owner-change", self.ao_copiar)
|
||||
self.criar_janela()
|
||||
|
||||
def do_command_line(self, linha):
|
||||
if "show" in linha.get_arguments()[1:]:
|
||||
self.mostrar()
|
||||
return 0
|
||||
|
||||
# ---------- captura ----------
|
||||
def ao_copiar(self, clip, _evento):
|
||||
if clip.wait_is_image_available():
|
||||
img = clip.wait_for_image()
|
||||
if img is None:
|
||||
return
|
||||
dados = img.save_to_bufferv("png", [], [])[1]
|
||||
nome = hashlib.sha1(dados).hexdigest()[:16] + ".png"
|
||||
caminho = os.path.join(PASTA, nome)
|
||||
if caminho == self.ignorar:
|
||||
return
|
||||
if not os.path.exists(caminho):
|
||||
with open(caminho, "wb") as f:
|
||||
f.write(dados)
|
||||
self.hist.adicionar("imagem", caminho)
|
||||
else:
|
||||
texto = clip.wait_for_text()
|
||||
if not texto or not texto.strip() or texto == self.ignorar:
|
||||
return
|
||||
self.hist.adicionar("texto", texto)
|
||||
if self.janela.get_visible():
|
||||
self.preencher()
|
||||
|
||||
# ---------- janela ----------
|
||||
def criar_janela(self):
|
||||
j = Gtk.Window(type=Gtk.WindowType.TOPLEVEL)
|
||||
self.add_window(j)
|
||||
j.set_decorated(False)
|
||||
j.set_resizable(False)
|
||||
j.set_skip_taskbar_hint(True)
|
||||
j.set_skip_pager_hint(True)
|
||||
j.set_keep_above(True)
|
||||
j.set_type_hint(Gdk.WindowTypeHint.DIALOG)
|
||||
j.set_default_size(LARGURA, ALTURA)
|
||||
visual = j.get_screen().get_rgba_visual()
|
||||
if visual:
|
||||
j.set_visual(visual)
|
||||
j.set_app_paintable(True)
|
||||
j.connect("draw", self.limpar_fundo)
|
||||
j.connect("key-press-event", self.ao_tecla)
|
||||
j.connect("focus-out-event", lambda *_: self.esconder())
|
||||
j.connect("delete-event", lambda *_: self.esconder() or True)
|
||||
|
||||
caixa = Gtk.Box()
|
||||
caixa.set_name("fundo")
|
||||
fundo = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
|
||||
for m in ("top", "bottom", "start", "end"):
|
||||
getattr(fundo, f"set_margin_{m}")(12)
|
||||
caixa.pack_start(fundo, True, True, 0)
|
||||
|
||||
topo = Gtk.Box(spacing=8)
|
||||
titulo = Gtk.Label(label="Área de transferência", xalign=0)
|
||||
titulo.set_name("titulo")
|
||||
limpar = Gtk.Button(label="Limpar tudo")
|
||||
limpar.set_name("limpar")
|
||||
limpar.connect("clicked", lambda *_: (self.hist.limpar(), self.preencher()))
|
||||
topo.pack_start(titulo, True, True, 0)
|
||||
topo.pack_end(limpar, False, False, 0)
|
||||
|
||||
self.lista = Gtk.ListBox()
|
||||
self.lista.set_selection_mode(Gtk.SelectionMode.NONE)
|
||||
self.lista.connect("row-activated", lambda _l, row: self.colar(row.item))
|
||||
rolagem = Gtk.ScrolledWindow()
|
||||
rolagem.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
||||
rolagem.add(self.lista)
|
||||
|
||||
self.vazio = Gtk.Label(
|
||||
label="Nada aqui ainda\n\nCopie algo com Ctrl+C e ele\naparecerá neste histórico.",
|
||||
justify=Gtk.Justification.CENTER)
|
||||
self.vazio.set_name("vazio")
|
||||
|
||||
self.pilha = Gtk.Stack()
|
||||
self.pilha.add_named(rolagem, "lista")
|
||||
self.pilha.add_named(self.vazio, "vazio")
|
||||
|
||||
fundo.pack_start(topo, False, False, 0)
|
||||
fundo.pack_start(self.pilha, True, True, 0)
|
||||
j.add(caixa)
|
||||
self.janela = j
|
||||
|
||||
@staticmethod
|
||||
def limpar_fundo(_w, cr):
|
||||
cr.set_source_rgba(0, 0, 0, 0)
|
||||
cr.set_operator(0) # CLEAR
|
||||
cr.paint()
|
||||
cr.set_operator(2) # OVER
|
||||
return False
|
||||
|
||||
def cartao(self, item):
|
||||
row = Gtk.ListBoxRow()
|
||||
row.item = item
|
||||
caixa = Gtk.Box(spacing=8)
|
||||
caixa.get_style_context().add_class("cartao")
|
||||
|
||||
if item["tipo"] == "imagem":
|
||||
try:
|
||||
pb = GdkPixbuf.Pixbuf.new_from_file_at_scale(item["dado"], 300, 110, True)
|
||||
conteudo = Gtk.Image.new_from_pixbuf(pb)
|
||||
conteudo.set_halign(Gtk.Align.START)
|
||||
except GLib.Error:
|
||||
conteudo = Gtk.Label(label="[imagem]", xalign=0)
|
||||
else:
|
||||
texto = item["dado"].strip()
|
||||
if len(texto) > 400:
|
||||
texto = texto[:400] + "…"
|
||||
conteudo = Gtk.Label(label=texto, xalign=0, yalign=0)
|
||||
conteudo.set_line_wrap(True)
|
||||
conteudo.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR)
|
||||
conteudo.set_lines(3)
|
||||
conteudo.set_ellipsize(Pango.EllipsizeMode.END)
|
||||
conteudo.set_max_width_chars(1)
|
||||
conteudo.get_style_context().add_class("texto")
|
||||
|
||||
botoes = Gtk.Box(spacing=2)
|
||||
botoes.set_valign(Gtk.Align.START)
|
||||
fixar = Gtk.Button.new_from_icon_name("view-pin-symbolic", Gtk.IconSize.MENU)
|
||||
fixar.set_tooltip_text("Desafixar" if item["fixo"] else "Fixar")
|
||||
fixar.get_style_context().add_class("icone")
|
||||
if item["fixo"]:
|
||||
fixar.get_style_context().add_class("fixado")
|
||||
fixar.connect("clicked", self.alternar_fixo, item)
|
||||
apagar = Gtk.Button.new_from_icon_name("user-trash-symbolic", Gtk.IconSize.MENU)
|
||||
apagar.set_tooltip_text("Excluir")
|
||||
apagar.get_style_context().add_class("icone")
|
||||
apagar.connect("clicked", lambda *_: (self.hist.remover(item), self.preencher()))
|
||||
botoes.pack_start(fixar, False, False, 0)
|
||||
botoes.pack_start(apagar, False, False, 0)
|
||||
|
||||
caixa.pack_start(conteudo, True, True, 0)
|
||||
caixa.pack_end(botoes, False, False, 0)
|
||||
row.add(caixa)
|
||||
return row
|
||||
|
||||
def alternar_fixo(self, _btn, item):
|
||||
item["fixo"] = not item["fixo"]
|
||||
self.hist.salvar()
|
||||
self.preencher()
|
||||
|
||||
def preencher(self):
|
||||
for filho in self.lista.get_children():
|
||||
self.lista.remove(filho)
|
||||
for item in self.hist.itens:
|
||||
self.lista.add(self.cartao(item))
|
||||
self.lista.show_all()
|
||||
self.pilha.set_visible_child_name("lista" if self.hist.itens else "vazio")
|
||||
primeira = self.lista.get_row_at_index(0)
|
||||
if primeira:
|
||||
primeira.grab_focus()
|
||||
|
||||
def mostrar(self):
|
||||
if self.janela.get_visible():
|
||||
self.esconder()
|
||||
return
|
||||
self.preencher()
|
||||
# posiciona perto do mouse, sem sair da tela
|
||||
tela = Gdk.Display.get_default()
|
||||
_s, x, y = tela.get_default_seat().get_pointer().get_position()[0:3]
|
||||
geo = tela.get_monitor_at_point(x, y).get_workarea()
|
||||
x = max(geo.x + 8, min(x - LARGURA // 2, geo.x + geo.width - LARGURA - 8))
|
||||
y = max(geo.y + 8, min(y - 40, geo.y + geo.height - ALTURA - 8))
|
||||
self.janela.move(x, y)
|
||||
self.janela.show_all()
|
||||
self.pilha.set_visible_child_name("lista" if self.hist.itens else "vazio")
|
||||
gw = self.janela.get_window()
|
||||
gw.focus(GdkX11.x11_get_server_time(gw))
|
||||
self.janela.present_with_time(GdkX11.x11_get_server_time(gw))
|
||||
|
||||
def esconder(self):
|
||||
self.janela.hide()
|
||||
return False
|
||||
|
||||
def ao_tecla(self, _w, ev):
|
||||
if ev.keyval == Gdk.KEY_Escape:
|
||||
self.esconder()
|
||||
return True
|
||||
if ev.keyval == Gdk.KEY_Delete:
|
||||
row = self.janela.get_focus()
|
||||
if isinstance(row, Gtk.ListBoxRow):
|
||||
self.hist.remover(row.item)
|
||||
self.preencher()
|
||||
return True
|
||||
return False
|
||||
|
||||
# ---------- colar ----------
|
||||
def colar(self, item):
|
||||
if item["tipo"] == "imagem":
|
||||
try:
|
||||
self.clip.set_image(GdkPixbuf.Pixbuf.new_from_file(item["dado"]))
|
||||
except GLib.Error:
|
||||
return
|
||||
else:
|
||||
self.clip.set_text(item["dado"], -1)
|
||||
self.clip.store()
|
||||
self.ignorar = item["dado"]
|
||||
# sobe o item para o topo, como no Windows
|
||||
self.hist.itens.remove(item)
|
||||
self.hist.itens.insert(0, item)
|
||||
self.hist.salvar()
|
||||
self.esconder()
|
||||
GLib.timeout_add(150, self.enviar_ctrl_v)
|
||||
|
||||
def enviar_ctrl_v(self):
|
||||
tecla = "ctrl+v"
|
||||
try:
|
||||
classe = subprocess.run(
|
||||
["xdotool", "getactivewindow", "getwindowclassname"],
|
||||
capture_output=True, text=True, timeout=1).stdout.lower()
|
||||
if any(t in classe for t in ("term", "tilix", "kitty", "alacritty", "konsole")):
|
||||
tecla = "ctrl+shift+v"
|
||||
subprocess.run(["xdotool", "key", "--clearmodifiers", tecla], timeout=2)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
pass
|
||||
GLib.timeout_add(500, self.liberar_ignorar)
|
||||
return False
|
||||
|
||||
def liberar_ignorar(self):
|
||||
self.ignorar = None
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(App().run(sys.argv))
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# Remove o Copiado e o atalho Win+V
|
||||
BASE=org.cinnamon.desktop.keybindings
|
||||
LISTA=$(gsettings get $BASE custom-list)
|
||||
for ID in $(python3 -c "import ast,sys; print(' '.join(ast.literal_eval(sys.argv[1].replace('@as ',''))))" "$LISTA"); do
|
||||
SCHEMA="$BASE.custom-keybinding:/org/cinnamon/desktop/keybindings/custom-keybindings/$ID/"
|
||||
if gsettings get "$SCHEMA" command | grep -q copiado; then
|
||||
LISTA=$(python3 -c "import ast,sys; l=ast.literal_eval(sys.argv[1].replace('@as ','')); print([x for x in l if x!='$ID'])" "$LISTA")
|
||||
gsettings set $BASE custom-list "$LISTA"
|
||||
gsettings reset-recursively "$SCHEMA"
|
||||
fi
|
||||
done
|
||||
pkill -f "/usr/bin/copiado" 2>/dev/null
|
||||
sudo apt-get remove -y copiado
|
||||
rm -rf "$HOME/.local/share/copiado"
|
||||
echo "Copiado removido. (O histórico salvo também foi apagado.)"
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
# Gera o pacote copiado_all.deb. Uso: empacotamento/gerar-deb.sh 1.2.3
|
||||
set -e
|
||||
VERSAO="${1:?informe a versão, ex.: 1.0.0}"
|
||||
cd "$(dirname "$0")/.."
|
||||
RAIZ=$(mktemp -d)
|
||||
chmod 755 "$RAIZ"
|
||||
trap 'rm -rf "$RAIZ"' EXIT
|
||||
|
||||
install -Dm755 copiado.py "$RAIZ/usr/bin/copiado"
|
||||
install -Dm644 README.md "$RAIZ/usr/share/doc/copiado/README.md"
|
||||
install -Dm644 /dev/stdin "$RAIZ/etc/xdg/autostart/copiado.desktop" <<DESK
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Copiado
|
||||
Comment=Histórico da área de transferência (Win+V)
|
||||
Exec=copiado
|
||||
NoDisplay=true
|
||||
X-GNOME-Autostart-enabled=true
|
||||
DESK
|
||||
|
||||
mkdir -p "$RAIZ/DEBIAN"
|
||||
cat > "$RAIZ/DEBIAN/control" <<CTRL
|
||||
Package: copiado
|
||||
Version: $VERSAO
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Depends: python3, python3-gi, gir1.2-gtk-3.0, xdotool
|
||||
Maintainer: Lucas Guerra <l.rayanguerra@gmail.com>
|
||||
Homepage: https://github.com/lucasrguerra/copiado
|
||||
Description: Histórico da área de transferência no estilo Windows 11
|
||||
Aperte Win+V para ver os últimos itens copiados e colar com um clique.
|
||||
CTRL
|
||||
|
||||
mkdir -p dist
|
||||
dpkg-deb --root-owner-group --build "$RAIZ" dist/copiado_all.deb
|
||||
echo "Gerado: dist/copiado_all.deb ($VERSAO)"
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
# Instala (ou atualiza) o Copiado a partir do pacote publicado no GitHub
|
||||
set -e
|
||||
URL="https://github.com/lucasrguerra/copiado/releases/latest/download/copiado_all.deb"
|
||||
BASE=org.cinnamon.desktop.keybindings
|
||||
TMP=$(mktemp -d)
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
echo "==> Baixando a versão mais recente..."
|
||||
curl -fsSL -o "$TMP/copiado_all.deb" "$URL"
|
||||
chmod 644 "$TMP/copiado_all.deb"
|
||||
|
||||
echo "==> Instalando o pacote..."
|
||||
sudo apt-get update -qq || echo " (aviso: algum repositório falhou no update, seguindo mesmo assim)"
|
||||
sudo apt-get install -y "$TMP/copiado_all.deb"
|
||||
|
||||
# remove instalações antigas feitas sem pacote (o histórico é mantido)
|
||||
pkill -f "share/copiado/copiado.py" 2>/dev/null || true
|
||||
pkill -f "share/clipboard-winv/clipboard-winv.py" 2>/dev/null || true
|
||||
rm -f "$HOME/.local/share/copiado/copiado.py" \
|
||||
"$HOME/.config/autostart/copiado.desktop" \
|
||||
"$HOME/.config/autostart/clipboard-winv.desktop"
|
||||
if [ -f "$HOME/.local/share/clipboard-winv/historico.json" ]; then
|
||||
mkdir -p "$HOME/.local/share/copiado"
|
||||
cp -a "$HOME/.local/share/clipboard-winv"/. "$HOME/.local/share/copiado"/
|
||||
sed -i "s#/clipboard-winv/#/copiado/#g" "$HOME/.local/share/copiado/historico.json"
|
||||
rm -rf "$HOME/.local/share/clipboard-winv"
|
||||
fi
|
||||
|
||||
echo "==> Criando atalho Win+V..."
|
||||
LISTA=$(gsettings get $BASE custom-list)
|
||||
ID=""
|
||||
for i in $(python3 -c "import ast,sys; print(' '.join(ast.literal_eval(sys.argv[1].replace('@as ',''))))" "$LISTA"); do
|
||||
B=$(gsettings get "$BASE.custom-keybinding:/org/cinnamon/desktop/keybindings/custom-keybindings/$i/" binding)
|
||||
[ "$B" = "['<Super>v']" ] && ID=$i && break
|
||||
done
|
||||
if [ -z "$ID" ]; then
|
||||
for i in $(seq 0 99); do
|
||||
echo "$LISTA" | grep -q "'custom$i'" || { ID="custom$i"; break; }
|
||||
done
|
||||
LISTA=$(python3 -c "import ast,sys; l=ast.literal_eval(sys.argv[1].replace('@as ','')); l.append('$ID'); print(l)" "$LISTA")
|
||||
fi
|
||||
SCHEMA="$BASE.custom-keybinding:/org/cinnamon/desktop/keybindings/custom-keybindings/$ID/"
|
||||
gsettings set "$SCHEMA" name "Copiado"
|
||||
gsettings set "$SCHEMA" command "copiado show"
|
||||
gsettings set "$SCHEMA" binding "['<Super>v']"
|
||||
# só agora registra a lista (e força o Cinnamon a recarregar os atalhos)
|
||||
gsettings set $BASE custom-list "[]"
|
||||
sleep 0.5
|
||||
gsettings set $BASE custom-list "$LISTA"
|
||||
|
||||
echo "==> Iniciando..."
|
||||
pkill -x copiado 2>/dev/null || pkill -f "/usr/bin/copiado" 2>/dev/null || true
|
||||
setsid copiado >/dev/null 2>&1 &
|
||||
|
||||
echo
|
||||
echo "Pronto! Versão instalada: $(dpkg-query -W -f='${Version}' copiado). Aperte Win+V."
|
||||
Reference in new issue
Block a user