diff --git a/topbar-desklet@SaMuKo/README.md b/topbar-desklet@SaMuKo/README.md new file mode 100644 index 000000000..e57bef957 --- /dev/null +++ b/topbar-desklet@SaMuKo/README.md @@ -0,0 +1,13 @@ +# Topbar Desklet + +Barra vertical desplegable con iconos de aplicaciones configurables. + +## Instalación + +1. Descarga el desklet desde la web de Cinnamon Spices. +2. Descomprime el archivo y mueve la carpeta `topbar-desklet@SaMuKo` a `~/.local/share/cinnamon/desklets/`. +3. Activa el desklet en la configuración de Cinnamon. + +## Uso + +Puedes configurar las aplicaciones que aparecen en la barra editando el archivo `apps_bar.json`. diff --git a/topbar-desklet@SaMuKo/files/topbar-desklet@SaMuKo/apps_bar.json b/topbar-desklet@SaMuKo/files/topbar-desklet@SaMuKo/apps_bar.json new file mode 100644 index 000000000..9ca7d6d6a --- /dev/null +++ b/topbar-desklet@SaMuKo/files/topbar-desklet@SaMuKo/apps_bar.json @@ -0,0 +1,17 @@ +[ + { + "name": "Disney+", + "icon": "chrome-mbjafbmjpcimpkkihihoideiofnoalmh-Default", + "command": "/opt/google/chrome/google-chrome --profile-directory=Default --app-id=mbjafbmjpcimpkkihihoideiofnoalmh %U" + }, + { + "name": "MAX", + "icon": "msedge-ldohheajbdblfgcgojahofgchabadgeg-Default", + "command": "/opt/microsoft/msedge/microsoft-edge --profile-directory=Default --app-id=ldohheajbdblfgcgojahofgchabadgeg --app-url=https://play.max.com/profile-picker" + }, + { + "name": "Netflix", + "icon": "msedge-eppojlglocelodeimnohnlnionkobfln-Default", + "command": "/opt/microsoft/msedge/microsoft-edge --profile-directory=Default --app-id=eppojlglocelodeimnohnlnionkobfln --app-url=https://www.netflix.com/browse" + } +] \ No newline at end of file diff --git a/topbar-desklet@SaMuKo/files/topbar-desklet@SaMuKo/desklet.js b/topbar-desklet@SaMuKo/files/topbar-desklet@SaMuKo/desklet.js new file mode 100644 index 000000000..2a0333574 --- /dev/null +++ b/topbar-desklet@SaMuKo/files/topbar-desklet@SaMuKo/desklet.js @@ -0,0 +1,325 @@ +// Importa las librerías necesarias de Cinnamon y Gjs +const Desklet = imports.ui.desklet; +const GLib = imports.gi.GLib; +const Clutter = imports.gi.Clutter; +const St = imports.gi.St; + +const Util = imports.misc.util; +const Tooltips = imports.ui.tooltips; +const PopupMenu = imports.ui.popupMenu; +const Main = imports.ui.main; +const ModalDialog = imports.ui.modalDialog; +var APPS = []; // Inicializa un array vacío para las aplicaciones + + +/* const APPS = [ + { name: 'Navegador', icon: 'firefox', command: 'firefox' }, + { name: 'Terminal', icon: 'utilities-terminal', command: 'gnome-terminal' }, + { name: 'Archivos', icon: 'nemo', command: 'nemo' }, + // Agrega más aplicaciones aquí +]; */ + +function TopbarDesklet(metadata, desklet_id) { + this._init(metadata, desklet_id); +} + +TopbarDesklet.prototype = { + __proto__: Desklet.Desklet.prototype, + + _init: function(metadata, desklet_id) { + Desklet.Desklet.prototype._init.call(this, metadata, desklet_id); + this._expanded = false; + this._focused_index = -1; + +// Lista de aplicaciones personalizables +let deskletPath = this.metadata.path; // dentro de la clase del desklet +let filePath = deskletPath + '/apps_bar.json'; +let [ok, contents, etag] = GLib.file_get_contents(filePath); +if (ok) { + APPS = JSON.parse(contents.toString()); + // Ahora apps es un array con tus aplicaciones +} + + + // El contenedor principal fue eliminado para evitar conflictos de parentesco. + + + // Tab para mostrar/ocultar barra + this.tab = new St.Button({ + style_class: 'topbar-tab', + reactive: true, + can_focus: true, + track_hover: true + }); + let tabIcon = new St.Icon({ + icon_name: 'applications-science', + icon_size: 24 + }); + this.tab.set_child(tabIcon); + this.tab.connect('clicked', () => this._toggleBar()); + + + this.mainTabIcon = new St.Button({ + style_class: 'topbar-tab', + reactive: true, + can_focus: true, + track_hover: true + }); + + let mainIcon = new St.Icon({ + icon_name: 'applications-science', + icon_size: 24 + }); + this.mainTabIcon.set_child(mainIcon); + this.mainTabIcon.connect('clicked', () => this._toggleBar()); + + // Agrega opción al menú contextual estándar del desklet + let self = this; + let configMenuItem = new PopupMenu.PopupMenuItem('Configurar aplicaciones'); + configMenuItem.connect('activate', () => { + // Ventana flotante NO modal, centrada y editable + let floatBox = new St.BoxLayout({ + vertical: true, + style_class: 'app-config-float', + reactive: true, + x_expand: false, + y_expand: false + }); + // Centrar en pantalla + let width = 500; + let height = 300; + floatBox.set_size(width, height); + floatBox.set_position(Math.floor((global.screen_width - width) / 2), Math.floor((global.screen_height - height) / 2)); + floatBox.set_style('background-color: rgba(30,30,30,0.97); border-radius: 16px; padding: 24px;'); + + let title = new St.Label({ text: 'Configurar aplicaciones', style_class: 'dialog-title' }); + let closebtn = new St.Button({ + style_class: 'close-button', + reactive: true, + can_focus: true, + track_hover: true + }); + closebtn.set_child(new St.Icon({ + icon_name: 'window-close', + icon_size: 16, + style_class: 'close-icon' + })); + closebtn.connect('clicked', () => { + Main.layoutManager.removeChrome(floatBox); + }); + + /* // Área de texto editable (una sola línea) con St.Entry + let textarea = new St.Entry({ + style_class: 'dialog-entry', + text: JSON.stringify(APPS, null, 2).replace(/\n/g, ' '), + x_expand: true, + y_expand: true, + can_focus: true, + track_hover: true, + hint_text: 'Edita el JSON de tus aplicaciones aquí' + }); */ + // Permitir edición y guardar cambios al cerrar + closebtn.connect('clicked', () => { + /* try { + APPS = JSON.parse(textarea.get_text()); + GLib.file_set_contents(filePath, JSON.stringify(APPS, null, 2)); + } catch (e) { + global.logError('Error al parsear JSON: ' + e.message); + } */ + Main.layoutManager.removeChrome(floatBox); + }); + + /* // Permitir Ctrl+Enter para guardar y cerrar + textarea.clutter_text.connect('key-press-event', (actor, event) => { + let symbol = event.get_key_symbol(); + if ((event.get_state() & Clutter.ModifierType.CONTROL_MASK) && symbol === Clutter.KEY_Return) { + closebtn.emit('clicked'); + return Clutter.EVENT_STOP; + } + return Clutter.EVENT_PROPAGATE; + }); */ + + // Botón para abrir el JSON en el editor de texto predeterminado + let openJsonBtn = new St.Button({ + style_class: 'open-json-btn', + label: 'Abrir JSON en editor', + reactive: true, + can_focus: true, + track_hover: true + }); + openJsonBtn.connect('clicked', () => { + Util.spawnCommandLine('xdg-open ' + filePath); + }); + + floatBox.add(title); + floatBox.add(closebtn); + floatBox.add(openJsonBtn); + /* floatBox.add(textarea); */ + floatBox.add(new St.Label({ text: 'Configura las aplicaciones que aparecerán en la barra superior.' })); + floatBox.add(new St.Label({ text: 'Puedes agregar o eliminar aplicaciones según tus preferencias.' })); + floatBox.add(new St.Label({ text: 'Ejemplo: { name: "Navegador", icon: "firefox", command: "firefox" }' })); + + + // Mostrar la ventana flotante correctamente + Main.layoutManager.addChrome(floatBox); + floatBox.raise_top(); + // El foco automático se elimina para evitar el warning de Clutter + }); + this._menu.addMenuItem(configMenuItem, 0); + + // Barra de aplicaciones (inicialmente oculta) + this.bar = new St.BoxLayout({ + vertical: false, // Corrected: horizontal layout + style_class: 'topbar-desklet', + visible: false, + width: 0, // Initial width for horizontal animation + height: 80, // Fixed height for horizontal layout + // x_align: St.Align.START, + reactive: true, + can_focus: true + }); + + + // + /* this.tab = new St.Button({ + style_class: 'topbar-tab', + reactive: true, + can_focus: true, + track_hover: true + }); */ + this.Maintab = new St.BoxLayout({ + style_class: 'desklet', // Clase de estilo base para desklets + vertical: true, // Los elementos dentro de 'tab' se apilarán verticalmente + reactive: true // Para que pueda recibir eventos de clic y arrastre + }); + this._refreshBar(); + + this.bar.connect('key-press-event', this._onKeyPress.bind(this)); + + + this.bar.add(this.tab); + + + this.Maintab.add(this.mainTabIcon); + + this.setContent(this.Maintab); + + Main.keybindingManager.addHotKey( + 'toggle-topbar', + metadata.keybindings['toggle-topbar'].value, + () => this._toggleBar() + ); + }, + + _toggleBar: function() { + this._expanded = !this._expanded; + if (this._expanded) { + this._showBar(); + } else { + this._hideBar(); + } + }, + + _showBar: function() { + this.bar.visible = true; + Main.layoutManager.addChrome(this.bar); + + this.bar.raise_top(); + Main.pushModal(this.bar); + this.bar.ease({ + opacity: 255, + width: this._getBarWidth(), + duration: 200, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + this._updateFocus(0); + } + }); + }, + + _hideBar: function() { + Main.popModal(this.bar); + this.bar.ease({ + opacity: 0, + width: 0, + duration: 200, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + this.bar.hide(); + Main.layoutManager.removeChrome(this.bar); + this._updateFocus(-1); + } + }); + }, + + _onKeyPress: function(actor, event) { + let symbol = event.get_key_symbol(); + let children = this.bar.get_children(); + if (children.length === 0) return Clutter.EVENT_PROPAGATE; + + let new_index = this._focused_index; + + if (symbol === Clutter.KEY_Left) { + new_index = (this._focused_index - 1 + children.length) % children.length; + } else if (symbol === Clutter.KEY_Right) { + new_index = (this._focused_index + 1) % children.length; + } else if (symbol === Clutter.KEY_Return || symbol === Clutter.KEY_KP_Enter) { + if (this._focused_index !== -1) { + children[this._focused_index].emit('clicked', children[this._focused_index]); + } + } else if (symbol === Clutter.KEY_Escape) { + this._toggleBar(); + } + else { + return Clutter.EVENT_PROPAGATE; + } + + if (new_index !== this._focused_index) { + this._updateFocus(new_index); + } + return Clutter.EVENT_STOP; + }, + + _updateFocus: function(new_index) { + let children = this.bar.get_children(); + if (this._focused_index !== -1 && this._focused_index < children.length) { + children[this._focused_index].remove_style_class_name('focused'); + } + this._focused_index = new_index; + if (this._focused_index !== -1 && this._focused_index < children.length) { + children[this._focused_index].add_style_class_name('focused'); + } + }, + + _getBarWidth: function() { + // 48px icono + 8px margen a cada lado (total 16px por icono) + 20px padding a cada lado de la barra (total 40px) + return APPS.length * (48 + 16) + 40; + }, + + _refreshBar: function() { + this.bar.destroy_all_children(); + APPS.forEach(app => { + let btn = new St.Button({ style_class: 'topbar-app-btn', reactive: true, can_focus: true }); + let icon = new St.Icon({ icon_name: app.icon, icon_size: 36, style_class: 'topbar-app-icon' }); + btn.set_child(icon); + btn.connect('clicked', () => { + Util.spawnCommandLine(app.command); + }); + new Tooltips.Tooltip(btn, app.name); + this.bar.add_actor(btn); + }); + }, + + on_desklet_removed: function() { + if (this._expanded) { + Main.popModal(this.bar); + Main.layoutManager.removeChrome(this.bar); + } + } +}; + +function main(metadata, desklet_id) { + return new TopbarDesklet(metadata, desklet_id); +} +// Ventana de configuración para agregar aplicaciones a la barra +const Mainloop = imports.mainloop; \ No newline at end of file diff --git a/topbar-desklet@SaMuKo/files/topbar-desklet@SaMuKo/metadata.json b/topbar-desklet@SaMuKo/files/topbar-desklet@SaMuKo/metadata.json new file mode 100644 index 000000000..b6c4fb40b --- /dev/null +++ b/topbar-desklet@SaMuKo/files/topbar-desklet@SaMuKo/metadata.json @@ -0,0 +1,16 @@ +{ + "uuid": "topbar-desklet@SaMuKo", + "name": "Topbar Desklet", + "description": "Barra vertical desplegable con iconos de aplicaciones configurables.", + "version": 1, + "max-instances": 1, + "multiversion": false, + "author": "SaMuKo", + "url": "https://github.com/SaMuKo/topbar-desklet", + "keybindings": { + "toggle-topbar": { + "name": "Toggle Topbar Desklet", + "value": "q" + } + } +} diff --git a/topbar-desklet@SaMuKo/files/topbar-desklet@SaMuKo/stylesheet.css b/topbar-desklet@SaMuKo/files/topbar-desklet@SaMuKo/stylesheet.css new file mode 100644 index 000000000..18a2b9fd3 --- /dev/null +++ b/topbar-desklet@SaMuKo/files/topbar-desklet@SaMuKo/stylesheet.css @@ -0,0 +1,258 @@ +/* Barra desklet vertical minimalista y moderna */ +.topbar-desklet-container { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: auto; + z-index: 9999; + pointer-events: auto; + align-items: center; + justify-content: flex-start; +} +.open-json-btn { + background: #4cb124; + color: #fff; + border: none; + border-radius: 8px; + padding: 12px 0; + font-size: 1.1em; + font-weight: bold; + width: 100%; + margin: 18px 0 12px 0; + box-shadow: 0 2px 8px rgba(0,0,0,0.12); + cursor: pointer; + transition: background 0.2s, color 0.2s; + outline: none; + text-align: center; + display: flex; + align-items: center; + justify-content: center; +} +.open-json-btn:hover { + background: #388e1f; + color: #e0ffe0; +} + /* La barra desklet principal */ + .topbar-desklet { + background: rgba(30, 30, 30, 0.97); /* Fondo oscuro semitransparente */ + border-radius: 18px; /* Esquinas redondeadas */ + box-shadow: 0 4px 24px rgba(0,0,0,0.35); /* Sombra sutil */ + padding: 12px 10px; /* Espaciado interno */ + min-width: 70px; /* Ancho mínimo */ + max-width: 100px; /* Ancho máximo */ + width: 80px; /* Ancho fijo */ + min-height: 0; /* Altura mínima */ + /* Se eliminó 'height: 0;' aquí para permitir que max-height controle la transición */ + max-height: 0; /* Altura inicial colapsada */ + display: flex; + flex-direction: row; /* Iconos apilados horizontalmente */ + align-items: center; /* Centrar iconos verticalmente */ + justify-content: flex-start; /* Alinear iconos al principio */ + transition: opacity 0.4s ease-out, max-width 0.4s ease-out; /* Transición suave para expandir/colapsar */ + margin-top: 0; + margin-left: 0; + overflow: hidden; /* Oculta el contenido cuando está colapsado */ + opacity: 0; /* Invisible cuando está colapsado */ + } + +.topbar-tab { + background: #4cb124; + border-radius: 0 0 10px 10px; + width: 80px; + height: 32px; + margin-left: 0; + margin-top: 0; + margin-bottom: 8px; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); + cursor: pointer; + transition: background 0.2s; + font-weight: bold; + color: #fff; + font-size: 16px; +} +.topbar-tab:hover { + background: #444; +} + +.topbar-app-btn { + background: transparent; + border: none; + margin: 0 8px; /* Ajustar margen para horizontal */ + padding: 0; + box-shadow: none; + transition: background 0.2s, transform 0.2s; + border-radius: 12px; + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; +} +.topbar-app-btn:hover { + background: rgba(255,255,255,0.10); + transform: scale(1.08); +} + +.topbar-app-btn.focused { + background: rgba(255,255,255,0.15); + border: 2px solid #4cb124; + transform: scale(1.08); +} + +.topbar-app-icon { + width: 36px; + height: 36px; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(219, 6, 6, 0.18); +} + +.app-config-dialog { + background-color: #23272e; + border-radius: 12px; + border: 2px solid #3a3f4b; + padding: 24px 32px; + min-width: 350px; + color: #f0f0f0; + box-shadow: 0 8px 32px rgba(0,0,0,0.35); +} + +.dialog-title { + font-size: 1.3em; + font-weight: bold; + margin-bottom: 18px; + color: #8ec07c; +} + +.dialog-entry { + background: #f0f0f0; + color: #141414; + border-radius: 6px; + border: 1px solid #444; + padding: 10px 12px; + min-width: 260px; + min-height: 120px; + max-width: 100%; + max-height: 300px; + font-family: monospace; + font-size: 1.05em; + margin: 10px 0; + box-shadow: 0 2px 8px rgba(0,0,0,0.10); + resize: vertical; + overflow-y: auto; + /* white-space: pre-wrap;*/ + line-height: 1.4; +} + +.dialog-add-btn, .dialog-save-btn, .dialog-cancel-btn, .dialog-del-btn { + background: #3a3f4b; + color: #f0f0f0; + border-radius: 6px; + border: none; + padding: 6px 14px; + margin: 6px 4px; + font-weight: bold; + transition: background 0.2s; +} +.dialog-add-btn:hover, .dialog-save-btn:hover, .dialog-cancel-btn:hover, .dialog-del-btn:hover { + background: #8ec07c; + color: #23272e; +} + +.app-config-float { + background-color: #23272e; + border-radius: 16px; + border: 2px solid #3a3f4b; + padding: 24px; + min-width: 350px; + min-height: 200px; + color: #f0f0f0; + box-shadow: 0 8px 32px rgba(0,0,0,0.35); + display: flex; + flex-direction: column; + align-items: stretch; + justify-content: flex-start; + z-index: 99999; +} + +.close-button { + background:#3a3f4b; + border: none; + border-radius: 8px; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: 16px; + right: 16px; + cursor: pointer; + transition: background 0.2s; +} +.close-button:hover { + background: rgba(255,255,255,0.08); +} + +.close-icon { + color: #e06c75; + width: 18px; + height: 18px; +} + + +/* Estilos para la ventana de configuración */ +.config-dialog { + background-color: #23272e; + border-radius: 12px; + border: 2px solid #3a3f4b; + padding: 24px 32px; + min-width: 350px; + color: #f0f0f0; + box-shadow: 0 8px 32px rgba(0,0,0,0.35); +} + +.config-title { + font-size: 1.3em; + font-weight: bold; + margin-bottom: 18px; + color: #8ec07c; +} + +.config-entry { + background: #f0f0f0; + color: #141414; + border-radius: 6px; + border: 1px solid #444; + padding: 10px 12px; + min-width: 260px; + min-height: 120px; + max-width: 100%; + max-height: 300px; + font-family: monospace; + font-size: 1.05em; + margin: 10px 0; + box-shadow: 0 2px 8px rgba(0,0,0,0.10); + resize: vertical; + overflow-y: auto; + /* white-space: pre-wrap;*/ + line-height: 1.4; +} + +.config-save-btn, .config-cancel-btn { + background: #3a3f4b; + color: #f0f0f0; + border-radius: 6px; + border: none; + padding: 6px 14px; + margin: 6px 4px; + font-weight: bold; + transition: background 0.2s; +} +.config-save-btn:hover, .config-cancel-btn:hover { + background: #8ec07c; + color: #23272e; +} \ No newline at end of file diff --git a/topbar-desklet@SaMuKo/index.html b/topbar-desklet@SaMuKo/index.html new file mode 100644 index 000000000..e69de29bb diff --git a/topbar-desklet@SaMuKo/info.json b/topbar-desklet@SaMuKo/info.json new file mode 100644 index 000000000..d428e4759 --- /dev/null +++ b/topbar-desklet@SaMuKo/info.json @@ -0,0 +1,7 @@ +{ + "uuid": "topbar-desklet@SaMuKo", + "name": "Topbar Desklet", + "description": "Barra vertical desplegable con iconos de aplicaciones configurables.", + "author": "SaMuKo", + "version": "1.0.0" +} \ No newline at end of file diff --git a/topbar-desklet@SaMuKo/main.js b/topbar-desklet@SaMuKo/main.js new file mode 100644 index 000000000..e69de29bb diff --git a/topbar-desklet@SaMuKo/style.css b/topbar-desklet@SaMuKo/style.css new file mode 100644 index 000000000..e69de29bb