'use strict'; import St from 'gi://St'; import Clutter from 'gi://Clutter'; import GLib from 'gi://GLib'; import Gio from 'gi://Gio'; import Soup from 'gi://Soup'; import GObject from 'gi://GObject'; import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; class QuotesExtension extends Extension { constructor(metadata) { super(metadata); this._quotes = []; this._currentQuoteIndex = 0; this._rotationTimer = null; this._syncTimer = null; this._indicator = null; this._session = null; this._dataDir = null; this._quotesFile = null; this._settings = null; } enable() { this._initializeSettings(); this._initializeDataDir(); this._loadQuotes(); this._createIndicator(); this._setupTimers(); this._syncQuotes(); } disable() { if (this._rotationTimer) { GLib.source_remove(this._rotationTimer); this._rotationTimer = null; } if (this._syncTimer) { GLib.source_remove(this._syncTimer); this._syncTimer = null; } if (this._indicator) { this._indicator.destroy(); this._indicator = null; } if (this._session) { this._session = null; } } _initializeDataDir() { this._dataDir = GLib.build_filenamev([GLib.get_user_data_dir(), 'quotes-extension']); GLib.mkdir_with_parents(this._dataDir, 0o755); const fileName = this._settings ? this._settings.get_string('quotes-file') || 'quotes.json' : 'quotes.json'; this._quotesFile = GLib.build_filenamev([this._dataDir, fileName]); } _initializeSettings() { this._settings = this.getSettings(); this._rotationInterval = this._settings.get_int('rotation-interval') || 30; this._syncInterval = this._settings.get_int('sync-interval') || 3600; this._apiUrl = this._settings.get_string('api-url') || 'https://api.quotable.io/random'; this._fontSize = this._settings.get_int('font-size') || 14; this._textColor = this._settings.get_string('text-color') || '#ffffff'; this._maxWidth = this._settings.get_int('max-width') || 60; this._quotesFileName = this._settings.get_string('quotes-file') || 'quotes.json'; this._useCustomQuotes = this._settings.get_boolean('use-custom-quotes'); this._customQuotes = this._settings.get_string('custom-quotes') || '[]'; // Listen for settings changes this._settings.connect('changed', (settings, key) => { if (key === 'font-size' || key === 'text-color') { this._updateLabelStyle(); } else if (key === 'max-width') { this._maxWidth = this._settings.get_int('max-width') || 60; this._label.set_text(this._getCurrentQuote()); } else if (key === 'quotes-file') { this._quotesFileName = this._settings.get_string('quotes-file') || 'quotes.json'; this._initializeDataDir(); this._loadQuotes(); } else if (key === 'use-custom-quotes' || key === 'custom-quotes') { this._useCustomQuotes = this._settings.get_boolean('use-custom-quotes'); this._customQuotes = this._settings.get_string('custom-quotes') || '[]'; this._loadQuotes(); if (this._label) { this._label.set_text(this._getCurrentQuote()); } } }); } _loadQuotes() { this._quotes = []; // Use custom quotes if enabled if (this._useCustomQuotes) { try { this._quotes = JSON.parse(this._customQuotes); if (!Array.isArray(this._quotes)) { this._quotes = []; } } catch (e) { console.error('Failed to parse custom quotes:', e); this._quotes = []; } } else { // Load from file try { if (GLib.file_test(this._quotesFile, GLib.FileTest.EXISTS)) { const [success, contents] = GLib.file_get_contents(this._quotesFile); if (success) { const decoder = new TextDecoder(); this._quotes = JSON.parse(decoder.decode(contents)); } } } catch (e) { console.error('Failed to load quotes:', e); } } if (this._quotes.length === 0) { this._quotes = [ { text: "The only way to do great work is to love what you do.", author: "Steve Jobs" }, { text: "Life is what happens to you while you're busy making other plans.", author: "John Lennon" }, { text: "The future belongs to those who believe in the beauty of their dreams.", author: "Eleanor Roosevelt" }, { text: "It is during our darkest moments that we must focus to see the light.", author: "Aristotle" }, { text: "Success is not final, failure is not fatal: it is the courage to continue that counts.", author: "Winston Churchill" }, { text: "The way to get started is to quit talking and begin doing.", author: "Walt Disney" }, { text: "Don't be afraid to give up the good to go for the great.", author: "John D. Rockefeller" }, { text: "Innovation distinguishes between a leader and a follower.", author: "Steve Jobs" }, { text: "Your time is limited, don't waste it living someone else's life.", author: "Steve Jobs" }, { text: "If you look at what you have in life, you'll always have more.", author: "Oprah Winfrey" }, { text: "The only impossible journey is the one you never begin.", author: "Tony Robbins" }, { text: "In the midst of winter, I found there was, within me, an invincible summer.", author: "Albert Camus" }, { text: "Be yourself; everyone else is already taken.", author: "Oscar Wilde" }, { text: "Two things are infinite: the universe and human stupidity; and I'm not sure about the universe.", author: "Albert Einstein" }, { text: "You miss 100% of the shots you don't take.", author: "Wayne Gretzky" }, { text: "Whether you think you can or you think you can't, you're right.", author: "Henry Ford" }, { text: "I have learned throughout my life as a composer chiefly through my mistakes and pursuits of false assumptions.", author: "Igor Stravinsky" }, { text: "The greatest glory in living lies not in never falling, but in rising every time we fall.", author: "Nelson Mandela" }, { text: "The only person you are destined to become is the person you decide to be.", author: "Ralph Waldo Emerson" }, { text: "What lies behind us and what lies before us are tiny matters compared to what lies within us.", author: "Ralph Waldo Emerson" }, { text: "Believe you can and you're halfway there.", author: "Theodore Roosevelt" }, { text: "The best time to plant a tree was 20 years ago. The second best time is now.", author: "Chinese Proverb" }, { text: "Don't judge each day by the harvest you reap but by the seeds that you plant.", author: "Robert Louis Stevenson" }, { text: "The only way to make sense out of change is to plunge into it, move with it, and join the dance.", author: "Alan Watts" }, { text: "Yesterday is history, tomorrow is a mystery, today is a gift of God, which is why we call it the present.", author: "Bill Keane" } ]; this._saveQuotes(); } } _saveQuotes() { try { const contents = JSON.stringify(this._quotes, null, 2); GLib.file_set_contents(this._quotesFile, contents); } catch (e) { console.error('Failed to save quotes:', e); } } _createIndicator() { this._indicator = new PanelMenu.Button(0.0, 'Quotes', false); this._label = new St.Label({ text: this._getCurrentQuote(), y_align: Clutter.ActorAlign.CENTER, style_class: 'quote-label' }); this._updateLabelStyle(); this._indicator.add_child(this._label); Main.panel.addToStatusArea('quotes', this._indicator, 1, 'left'); this._indicator.connect('button-press-event', () => { this._nextQuote(); }); } _getCurrentQuote() { if (this._quotes.length === 0) return 'No quotes available'; const quote = this._quotes[this._currentQuoteIndex]; let text = `"${quote.text}" - ${quote.author}`; if (text.length > this._maxWidth) { text = text.substring(0, this._maxWidth - 3) + '...'; } return text; } _updateLabelStyle() { if (!this._label) return; this._fontSize = this._settings.get_int('font-size') || 14; this._textColor = this._settings.get_string('text-color') || '#ffffff'; const style = `font-size: ${this._fontSize}px; color: ${this._textColor};`; this._label.set_style(style); } _nextQuote() { if (this._quotes.length === 0) return; this._currentQuoteIndex = (this._currentQuoteIndex + 1) % this._quotes.length; this._label.set_text(this._getCurrentQuote()); } _setupTimers() { this._rotationTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, this._rotationInterval, () => { this._nextQuote(); return GLib.SOURCE_CONTINUE; }); this._syncTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, this._syncInterval, () => { this._syncQuotes(); return GLib.SOURCE_CONTINUE; }); } _syncQuotes() { try { this._session = new Soup.Session(); const message = Soup.Message.new('GET', this._apiUrl); this._session.send_and_read_async(message, GLib.PRIORITY_DEFAULT, null, (session, result) => { try { const bytes = session.send_and_read_finish(result); const decoder = new TextDecoder(); const response = decoder.decode(bytes.get_data()); const data = JSON.parse(response); if (data.content && data.author) { const newQuote = { text: data.content, author: data.author }; const exists = this._quotes.some(q => q.text === newQuote.text && q.author === newQuote.author); if (!exists) { this._quotes.push(newQuote); this._saveQuotes(); } } } catch (e) { console.error('Failed to sync quotes:', e); } }); } catch (e) { console.error('Failed to create sync session:', e); } } } export default class QuotesGnomeExtension extends Extension { enable() { this._extension = new QuotesExtension(this.metadata); this._extension.enable(); } disable() { if (this._extension) { this._extension.disable(); this._extension = null; } } }