|
| 1 | +import QtQuick |
| 2 | +import Quickshell |
| 3 | +import Quickshell.Io |
| 4 | +import qs.Commons |
| 5 | +import qs.Services.UI |
| 6 | + |
| 7 | +Item { |
| 8 | + id: root |
| 9 | + property var pluginApi: null |
| 10 | + |
| 11 | + property var cfg: pluginApi?.pluginSettings || ({}) |
| 12 | + property var defaults: pluginApi?.manifest?.metadata?.defaultSettings || ({}) |
| 13 | + readonly property bool fortuneEnabled: cfg.fortuneEnabled ?? defaults.fortuneEnabled ?? false |
| 14 | + readonly property bool fortuneOffensive: cfg.fortuneOffensive ?? defaults.fortuneOffensive ?? false |
| 15 | + readonly property bool fortuneEqual: cfg.fortuneEqual ?? defaults.fortuneEqual ?? false |
| 16 | + readonly property string fortuneCategory: cfg.fortuneCategory ?? defaults.fortuneCategory ?? "" |
| 17 | + readonly property int fortuneMaxLength: cfg.fortuneMaxLength ?? defaults.fortuneMaxLength ?? 60 |
| 18 | + readonly property bool listEnabled: cfg.listEnabled ?? defaults.listEnabled ?? false |
| 19 | + readonly property string textFile: cfg.textFile ?? defaults.textFile ?? "" |
| 20 | + readonly property bool refreshOnWallpaper: cfg.refreshOnWallpaper ?? defaults.refreshOnWallpaper ?? true |
| 21 | + |
| 22 | + property string fortuneText: "" |
| 23 | + property int _retries: 0 |
| 24 | + readonly property int _maxRetries: 10 |
| 25 | + |
| 26 | + property string listText: "" |
| 27 | + readonly property string _examplesPath: Qt.resolvedUrl("examples.txt").toString().replace(/^file:\/\//, "") |
| 28 | + readonly property string _activePath: textFile.trim().length > 0 ? textFile.trim() : _examplesPath |
| 29 | + |
| 30 | + Component.onCompleted: { |
| 31 | + if (fortuneEnabled) triggerFortune(); |
| 32 | + if (listEnabled) pickFromFile(); |
| 33 | + } |
| 34 | + |
| 35 | + onFortuneEnabledChanged: { |
| 36 | + if (fortuneEnabled) triggerFortune(); |
| 37 | + } |
| 38 | + |
| 39 | + onFortuneOffensiveChanged: { |
| 40 | + if (fortuneEnabled) triggerFortune(); |
| 41 | + } |
| 42 | + |
| 43 | + onFortuneEqualChanged: { |
| 44 | + if (fortuneEnabled) triggerFortune(); |
| 45 | + } |
| 46 | + |
| 47 | + onFortuneCategoryChanged: { |
| 48 | + if (fortuneEnabled) triggerFortune(); |
| 49 | + } |
| 50 | + |
| 51 | + onListEnabledChanged: { |
| 52 | + if (listEnabled) pickFromFile(); |
| 53 | + } |
| 54 | + |
| 55 | + onTextFileChanged: { |
| 56 | + if (listEnabled) pickFromFile(); |
| 57 | + } |
| 58 | + |
| 59 | + // Debounce timer — wallpaperChanged fires once per screen, wait for all to settle |
| 60 | + Timer { |
| 61 | + id: debounce |
| 62 | + interval: 300 |
| 63 | + repeat: false |
| 64 | + onTriggered: { |
| 65 | + if (root.fortuneEnabled) triggerFortune(); |
| 66 | + if (root.listEnabled) pickFromFile(); |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + Connections { |
| 71 | + target: WallpaperService |
| 72 | + function onWallpaperChanged(screenName, path) { |
| 73 | + if (!root.refreshOnWallpaper) return; |
| 74 | + if (root.fortuneEnabled || root.listEnabled) debounce.restart(); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + Process { |
| 79 | + id: fortuneProcess |
| 80 | + command: { |
| 81 | + var cmd = ["fortune", "-s"]; |
| 82 | + if (root.fortuneOffensive) cmd.push("-o"); |
| 83 | + if (root.fortuneEqual) cmd.push("-e"); |
| 84 | + if (root.fortuneCategory.trim().length > 0) cmd.push(root.fortuneCategory.trim()); |
| 85 | + return cmd; |
| 86 | + } |
| 87 | + running: false |
| 88 | + stdout: StdioCollector { |
| 89 | + onStreamFinished: { |
| 90 | + var lines = text.trim().split('\n').filter(l => l.trim().length > 0); |
| 91 | + var valid = lines.length === 1 && lines[0].length <= root.fortuneMaxLength; |
| 92 | + if (valid) { |
| 93 | + root.fortuneText = lines[0].trim(); |
| 94 | + root._retries = 0; |
| 95 | + } else if (root._retries < root._maxRetries) { |
| 96 | + root._retries++; |
| 97 | + root.triggerFortune(); |
| 98 | + } else { |
| 99 | + Logger.w("NotJustText", "Gave up after", root._maxRetries, "retries finding a short single-line fortune"); |
| 100 | + root.fortuneText = root.pluginApi?.tr("fortune.gaveUp"); |
| 101 | + root._retries = 0; |
| 102 | + } |
| 103 | + } |
| 104 | + } |
| 105 | + onExited: (exitCode, exitStatus) => { |
| 106 | + if (exitCode === 127) { |
| 107 | + Logger.e("NotJustText", "fortune is not installed — install it to use fortune mode"); |
| 108 | + root.fortuneText = root.pluginApi?.tr("fortune.notInstalled"); |
| 109 | + } else if (exitCode !== 0) { |
| 110 | + Logger.w("NotJustText", "fortune exited with code", exitCode); |
| 111 | + } |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + function triggerFortune() { |
| 116 | + fortuneProcess.running = false; |
| 117 | + fortuneProcess.running = true; |
| 118 | + } |
| 119 | + |
| 120 | + Process { |
| 121 | + id: textFileProcess |
| 122 | + command: ["cat", root._activePath] |
| 123 | + running: false |
| 124 | + stdout: StdioCollector { |
| 125 | + onStreamFinished: { |
| 126 | + var lines = text.split('\n').filter(l => l.trim().length > 0 && !l.trim().startsWith('# ')); |
| 127 | + if (lines.length > 0) { |
| 128 | + root.listText = lines[Math.floor(Math.random() * lines.length)].trim(); |
| 129 | + } else { |
| 130 | + root.listText = ""; |
| 131 | + } |
| 132 | + } |
| 133 | + } |
| 134 | + onExited: (exitCode, exitStatus) => { |
| 135 | + if (exitCode !== 0) { |
| 136 | + Logger.w("NotJustText", "Could not read text file:", root._activePath); |
| 137 | + root.listText = ""; |
| 138 | + } |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + function pickFromFile() { |
| 143 | + textFileProcess.running = false; |
| 144 | + textFileProcess.running = true; |
| 145 | + } |
| 146 | +} |
0 commit comments