#Requires AutoHotkey v2.0 #SingleInstance Force #UseHook True #Warn #MaxThreadsPerHotkey 1 ; ============================================================ ; RANURAS DE PORTAPAPELES - archivo unico, sin dependencias ; ; Win + Alt izq + V -> modo PEGAR -> pulsa 1..9 ; Win + Alt izq + C -> modo COPIAR -> pulsa 1..9 ; Win + Alt izq + 0 -> ver el panel de ranuras ; Win + Alt izq + F5 -> recargar el script ; Esc -> cancelar el modo ; ; Por que Win+Alt y no Ctrl+Alt: Office se reserva media docena ; de Ctrl+Alt+letra (V = Pegado especial, C = ©, R = ®, T = ™, ; D/F/M = notas y comentarios...). La tecla Windows no la usa ; ninguna aplicacion de Office, asi que Win+Alt+V y Win+Alt+C ; quedan libres y conservan la memoria de Ctrl+V / Ctrl+C. ; Se exige Alt IZQUIERDO porque AltGr es Alt derecho. ; Dentro de esta familia, Game Bar ocupa Win+Alt+R, G, B y D: ; por eso la recarga esta en F5 y no en R. ; ; El dato vive en ranuras.txt, junto a este archivo. Se crea solo ; la primera vez. Editalo con el Bloc de notas cuando quieras. ; ; Diagnostico: AutoHotkey64.exe Portapapeles.ahk --probar ; ============================================================ ; ---- lo unico que se toca para configurar ---- TOTAL_RANURAS := 9 ; de 1 a 9; los atajos se generan solos LIDER := "# 0 && A_Args[1] = "--probar") { Autoprueba() ExitApp() } ; ============================================================ ; Atajos: se generan segun TOTAL_RANURAS, no estan escritos a mano ; ============================================================ RegistrarAtajos() { puestos := 0, intentados := 0 intentados += 1, puestos += Poner(LIDER TECLA_PEGAR, (*) => EntrarModo("pegar")) intentados += 1, puestos += Poner(LIDER TECLA_COPIAR, (*) => EntrarModo("copiar")) intentados += 1, puestos += Poner(LIDER TECLA_PANEL, (*) => MostrarPanel(PANEL_MS)) intentados += 1, puestos += Poner(LIDER TECLA_RECARGAR, (*) => Reload()) ; Nombres del teclado numerico con NumLock apagado, en orden 1..9. sinNumLock := ["NumpadEnd", "NumpadDown", "NumpadPgDn", "NumpadLeft", "NumpadClear", "NumpadRight", "NumpadHome", "NumpadUp", "NumpadPgUp"] HotIf (*) => modo != "" Loop Min(TOTAL_RANURAS, 9) { numero := A_Index ; .Bind fija el numero AHORA. Una funcion flecha capturaria la ; variable, no su valor, y las nueve ranuras acabarian siendo la ultima. accion := UsarRanura.Bind(numero) intentados += 3 puestos += Poner("*" numero, accion) puestos += Poner("*Numpad" numero, accion) puestos += Poner("*" sinNumLock[numero], accion) } intentados += 1, puestos += Poner("*Esc", (*) => CancelarModo()) HotIf return {puestos: puestos, intentados: intentados} } Poner(atajo, accion) { try { Hotkey(atajo, accion) return 1 } catch Error as e { Aviso("No se pudo registrar " atajo ": " e.Message) return 0 } } PrepararBandeja() { A_IconTip := "Ranuras de portapapeles`nWin+Alt+V pegar · Win+Alt+C copiar" A_TrayMenu.Insert("1&", "Ver las ranuras", (*) => MostrarPanel(PANEL_MS)) A_TrayMenu.Insert("2&", "Abrir " NOMBRE_ARCHIVO, (*) => AbrirArchivo()) A_TrayMenu.Insert("3&") } AbrirArchivo() { try Run(ARCHIVO_RANURAS) catch Error as e Aviso("No se pudo abrir el archivo: " e.Message) } ; ============================================================ ; Ciclo de vida del modo ; ============================================================ EntrarModo(nuevoModo) { global modo modo := nuevoModo SetTimer(CancelarModo, 0) MostrarPanel(0) SetTimer(CancelarModo, -MODO_CADUCA_MS) } CancelarModo() { global modo modo := "" SetTimer(CancelarModo, 0) OcultarPanel() } UsarRanura(indice, *) { accion := modo CancelarModo() if (indice > TOTAL_RANURAS) return if (accion = "pegar") PegarRanura(indice) else if (accion = "copiar") GuardarRanura(indice) } ; ============================================================ ; Acciones ; ============================================================ PegarRanura(indice) { global respaldoClip valores := LeerRanuras() texto := valores[indice] if (texto = "") { Aviso("Ranura " indice " vacia") return } respaldoClip := ClipboardAll() A_Clipboard := texto if !ClipWait(CLIP_LISTO_S, 0) { Aviso("El portapapeles no acepto el texto") return } SoltarModificadores() remoto := EsEscritorioRemoto() if remoto { SendEvent("{Ctrl down}") Sleep(14) SendEvent("{v down}") Sleep(22) SendEvent("{v up}") Sleep(12) SendEvent("{Ctrl up}") } else { SendInput("^v") } Aviso("Ranura " indice " pegada: " Resumir(texto, 40), remoto) if (CLIP_RESTAURA_MS > 0) { SetTimer(RestaurarPortapapeles, 0) SetTimer(RestaurarPortapapeles, -CLIP_RESTAURA_MS) } } GuardarRanura(indice) { respaldo := ClipboardAll() previo := A_Clipboard A_Clipboard := "" SoltarModificadores() SendInput("^c") if ClipWait(COPIA_ESPERA_S, 0) { texto := A_Clipboard origen := "seleccion" } else { texto := previo origen := "portapapeles" try A_Clipboard := respaldo } if (texto = "") { Aviso("No habia nada seleccionado ni en el portapapeles") return } if EscribirRanura(indice, texto) Aviso("Ranura " indice " <- " origen ": " Resumir(texto, 40)) } RestaurarPortapapeles() { global respaldoClip SetTimer(RestaurarPortapapeles, 0) if !IsObject(respaldoClip) return try A_Clipboard := respaldoClip respaldoClip := "" } ; ============================================================ ; El archivo de ranuras ; ; Formato: una linea por ranura, numero:valor ; Se lee entero en cada uso: son unos pocos bytes, y asi lo que ; editas a mano surte efecto al instante, sin recargar nada. ; ============================================================ LeerRanuras() { valores := [] Loop TOTAL_RANURAS valores.Push("") if !FileExist(ARCHIVO_RANURAS) { CrearArchivo(valores) return valores } contenido := "" try contenido := FileRead(ARCHIVO_RANURAS, "UTF-8") catch Error as e { Aviso("No se pudo leer " NOMBRE_ARCHIVO ": " e.Message) return valores } for linea in StrSplit(contenido, "`n", "`r") { linea := Trim(linea) if (linea = "" || SubStr(linea, 1, 1) = "#") continue posicion := InStr(linea, ":") if !posicion continue numero := Trim(SubStr(linea, 1, posicion - 1)) if !IsInteger(numero) continue numero := Integer(numero) if (numero < 1 || numero > TOTAL_RANURAS) continue valores[numero] := Descodificar(SubStr(linea, posicion + 1)) } return valores } EscribirRanura(indice, texto) { valores := LeerRanuras() valores[indice] := texto return CrearArchivo(valores) } CrearArchivo(valores) { texto := "# Ranuras del portapapeles. Una linea por ranura: numero:valor`r`n" texto .= "# Lo que escribas aqui es lo que se pega. Guarda y listo, sin recargar.`r`n" texto .= "# Un salto de linea dentro de un valor se escribe \n (barra invertida + n).`r`n" texto .= "#`r`n" for indice, valor in valores texto .= indice ":" Codificar(valor) "`r`n" try { archivo := FileOpen(ARCHIVO_RANURAS, "w", "UTF-8") if !IsObject(archivo) { Aviso("No se pudo escribir en " ARCHIVO_RANURAS) return false } archivo.Write(texto) archivo.Close() return true } catch Error as e { Aviso("No se pudo escribir en " NOMBRE_ARCHIVO ": " e.Message) return false } } ; Una ranura cabe en una linea, asi que los saltos y tabuladores viajan ; escapados. Es lo unico que el formato necesita saber. Codificar(texto) { salida := StrReplace(texto, "\", "\\") salida := StrReplace(salida, "`r`n", "\n") salida := StrReplace(salida, "`n", "\n") salida := StrReplace(salida, "`r", "\n") salida := StrReplace(salida, "`t", "\t") return salida } Descodificar(texto) { salida := "" posicion := 1 largo := StrLen(texto) while (posicion <= largo) { caracter := SubStr(texto, posicion, 1) if (caracter = "\" && posicion < largo) { switch SubStr(texto, posicion + 1, 1) { case "n": salida .= "`r`n" case "t": salida .= "`t" case "\": salida .= "\" default: salida .= SubStr(texto, posicion + 1, 1) } posicion += 2 continue } salida .= caracter posicion += 1 } return salida } ; El archivo va junto al script. Si esa carpeta es de solo lectura ; (Program Files, un pendrive protegido, una carpeta de red), cae en ; el perfil del usuario en vez de fallar en silencio. RutaDatos() { if EsEscribible(A_ScriptDir) return A_ScriptDir "\" NOMBRE_ARCHIVO carpeta := A_AppData "\Portapapeles" if !DirExist(carpeta) { try DirCreate(carpeta) } return carpeta "\" NOMBRE_ARCHIVO } EsEscribible(carpeta) { sonda := carpeta "\~sonda-" A_TickCount ".tmp" try { archivo := FileOpen(sonda, "w") if !IsObject(archivo) return false archivo.Close() FileDelete(sonda) ; borra unicamente la sonda recien creada return true } return false } ; ============================================================ ; Panel y avisos ; ============================================================ MostrarPanel(duracionMs := 0) { valores := LeerRanuras() if (modo = "pegar") titulo := "PEGAR - elige ranura 1-" TOTAL_RANURAS else if (modo = "copiar") titulo := "COPIAR lo seleccionado - elige ranura 1-" TOTAL_RANURAS else titulo := "RANURAS" regla := "--------------------------------------------------------" texto := titulo "`n" regla "`n" for indice, valor in valores texto .= " " indice " " (valor = "" ? "(vacia)" : Resumir(valor, RECORTE_PANEL)) "`n" texto .= regla "`nEsc cancela" SetTimer(OcultarPanel, 0) ToolTip(texto, panelX, panelY) if (duracionMs > 0) SetTimer(OcultarPanel, -duracionMs) } Aviso(mensaje, remoto := false) { global ultimoAviso ultimoAviso := mensaje SetTimer(OcultarPanel, 0) ToolTip(remoto ? mensaje " [REMOTO]" : mensaje, panelX, panelY) SetTimer(OcultarPanel, -AVISO_MS) } OcultarPanel() { SetTimer(OcultarPanel, 0) ToolTip() } ; ============================================================ ; Utilidades ; ============================================================ Resumir(texto, limite) { limpio := StrReplace(texto, "`r`n", " / ") limpio := StrReplace(limpio, "`n", " / ") limpio := StrReplace(limpio, "`r", " / ") limpio := StrReplace(limpio, "`t", " ") if (StrLen(limpio) > limite) limpio := SubStr(limpio, 1, limite - 3) "..." return limpio } EsEscritorioRemoto() { static remotos := ["mstsc.exe", "msrdc.exe", "RdClient.Windows.exe", "Windows365.exe"] for ejecutable in remotos { if WinActive("ahk_exe " ejecutable) return true } return WinActive("ahk_class TscShellContainerClass") ? true : false } SoltarModificadores() { SendEvent("{Ctrl up}{Alt up}{Shift up}{LWin up}{RWin up}") Sleep(6) } AlSalir(*) { SendEvent("{Ctrl up}{Alt up}{Shift up}{LWin up}{RWin up}") } ; ============================================================ ; Autoprueba (--probar) ; ============================================================ Autoprueba() { salida := "AUTOPRUEBA" "`n" salida .= "version AHK: " A_AhkVersion "`n" salida .= "archivo : " ARCHIVO_RANURAS "`n" salida .= "escribible : " (EsEscribible(A_ScriptDir) ? "si, junto al script" : "no, se usa el perfil de usuario") "`n" salida .= "atajos : " atajosPuestos.puestos "/" atajosPuestos.intentados " registrados`n" inicio := A_TickCount valores := LeerRanuras() salida .= "lectura : " (A_TickCount - inicio) " ms`n" for indice, valor in valores salida .= " " indice " " (valor = "" ? "(vacia)" : Resumir(valor, 50)) "`n" original := valores[TOTAL_RANURAS] testigo := "linea uno`r`nlinea dos`tcon tabulador y \ barra" inicio := A_TickCount escrito := EscribirRanura(TOTAL_RANURAS, testigo) salida .= "escritura : " (escrito ? "ok" : "FALLO") " en " (A_TickCount - inicio) " ms`n" inicio := A_TickCount releidas := LeerRanuras() coincide := releidas[TOTAL_RANURAS] = testigo salida .= "ida y vuelta con saltos, tabulador y barra: " (coincide ? "COINCIDE" : "NO COINCIDE") " en " (A_TickCount - inicio) " ms`n" EscribirRanura(TOTAL_RANURAS, original) final := LeerRanuras() salida .= "restaurado : " (final[TOTAL_RANURAS] = original ? "si" : "NO") "`n" if (ultimoAviso != "") salida .= "ultimo aviso: " ultimoAviso "`n" todo := escrito && coincide && atajosPuestos.puestos = atajosPuestos.intentados salida .= "veredicto : " (todo ? "OK" : "REVISAR") "`n" FileAppend(salida, "*") }