diff --git a/.config/ags/assets/icons/ai-openai-symbolic.svg b/.config/ags/assets/icons/ai-openai-symbolic.svg new file mode 120000 index 000000000..c9ee0b32f --- /dev/null +++ b/.config/ags/assets/icons/ai-openai-symbolic.svg @@ -0,0 +1 @@ +openai-symbolic.svg \ No newline at end of file diff --git a/.config/ags/assets/icons/ai-oxygen-symbolic.svg b/.config/ags/assets/icons/ai-oxygen-symbolic.svg new file mode 100644 index 000000000..5e1cc1937 --- /dev/null +++ b/.config/ags/assets/icons/ai-oxygen-symbolic.svg @@ -0,0 +1,54 @@ + + + + + + + + + + diff --git a/.config/ags/assets/icons/ai-zukijourney.png b/.config/ags/assets/icons/ai-zukijourney.png new file mode 100644 index 000000000..917335e72 Binary files /dev/null and b/.config/ags/assets/icons/ai-zukijourney.png differ diff --git a/.config/ags/config.js b/.config/ags/config.js index 2e13709ef..59d5e74ba 100644 --- a/.config/ags/config.js +++ b/.config/ags/config.js @@ -17,6 +17,7 @@ import Session from './modules/session/main.js'; import SideLeft from './modules/sideleft/main.js'; import SideRight from './modules/sideright/main.js'; +const COMPILED_STYLE_DIR = `${GLib.get_user_cache_dir()}/ags/user/generated` const range = (length, start = 1) => Array.from({ length }, (_, i) => i + start); function forMonitors(widget) { const n = Gdk.Display.get_default()?.get_n_monitors() || 1; @@ -27,9 +28,8 @@ function forMonitors(widget) { Utils.exec(`bash -c 'echo "" > ${App.configDir}/scss/_musicwal.scss'`); // reset music styles Utils.exec(`bash -c 'echo "" > ${App.configDir}/scss/_musicmaterial.scss'`); // reset music styles async function applyStyle() { - const COMPILED_STYLE_DIR = `${GLib.get_user_cache_dir()}/ags/user/generated` Utils.exec(`mkdir -p ${COMPILED_STYLE_DIR}`); - Utils.exec(`sassc ${App.configDir}/scss/main.scss ${COMPILED_STYLE_DIR}/style.css`); + Utils.exec(`sass ${App.configDir}/scss/main.scss ${COMPILED_STYLE_DIR}/style.css`); App.resetCss(); App.applyCss(`${COMPILED_STYLE_DIR}/style.css`); console.log('[LOG] Styles loaded') @@ -56,7 +56,7 @@ const Windows = () => [ ]; const CLOSE_ANIM_TIME = 210; // Longer than actual anim time to make sure widgets animate fully export default { - css: `${App.configDir}/style.css`, + css: `${COMPILED_STYLE_DIR}/style.css`, stackTraceOnError: true, closeWindowDelay: { // For animations 'sideright': CLOSE_ANIM_TIME, diff --git a/.config/ags/modules/.miscutils/md2pango.js b/.config/ags/modules/.miscutils/md2pango.js index 054752b14..1c868757b 100644 --- a/.config/ags/modules/.miscutils/md2pango.js +++ b/.config/ags/modules/.miscutils/md2pango.js @@ -83,4 +83,7 @@ console.log('uwu'); \`\`\` - Random instruction thing - To update arch lincox, run \`sudo pacman -Syu\` +\`\`\`tex +\\frac{d}{dx} \\left( \\frac{x-438}{x^2+23x-7} \\right) = \\frac{-x^2 + 869}{(x^2+23x-7)^2} hmmmmmm \\frac{d}{dx} \\left( \\frac{x-438}{x^2+23x-7} \\right) = \\frac{-x^2 + 869}{(x^2+23x-7)^2} +\`\`\` `; \ No newline at end of file diff --git a/.config/ags/modules/bar/music.js b/.config/ags/modules/bar/music.js index f0865b879..9dc4b013a 100644 --- a/.config/ags/modules/bar/music.js +++ b/.config/ags/modules/bar/music.js @@ -1,12 +1,21 @@ +const { GLib } = imports.gi; import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js'; -const { Box, Label, Overlay, Revealer } = Widget; +const { Box, Button, EventBox, Label, Overlay, Revealer, Scrollable } = Widget; const { execAsync, exec } = Utils; import { AnimatedCircProg } from "../.commonwidgets/cairo_circularprogress.js"; import { MaterialIcon } from '../.commonwidgets/materialicon.js'; import { showMusicControls } from '../../variables.js'; +const CUSTOM_MODULE_CONTENT_INTERVAL_FILE = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-interval.txt`; +const CUSTOM_MODULE_CONTENT_SCRIPT = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-poll.sh`; +const CUSTOM_MODULE_LEFTCLICK_SCRIPT = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-leftclick.sh`; +const CUSTOM_MODULE_RIGHTCLICK_SCRIPT = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-rightclick.sh`; +const CUSTOM_MODULE_MIDDLECLICK_SCRIPT = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-middleclick.sh`; +const CUSTOM_MODULE_SCROLLUP_SCRIPT = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-scrollup.sh`; +const CUSTOM_MODULE_SCROLLDOWN_SCRIPT = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-scrolldown.sh`; + function trimTrackTitle(title) { if (!title) return ''; const cleanPatterns = [ @@ -17,10 +26,10 @@ function trimTrackTitle(title) { return title; } -const BarGroup = ({ child }) => Widget.Box({ +const BarGroup = ({ child }) => Box({ className: 'bar-group-margin bar-sides', children: [ - Widget.Box({ + Box({ className: 'bar-group bar-group-standalone bar-group-pad-system', children: [child], }), @@ -34,7 +43,7 @@ const BarResource = (name, icon, command) => { hpack: 'center', }); const resourceProgress = Overlay({ - child: Widget.Box({ + child: Box({ vpack: 'center', className: 'bar-batt', homogeneous: true, @@ -93,14 +102,14 @@ const switchToRelativeWorkspace = async (self, num) => { export default () => { // TODO: use cairo to make button bounce smaller on click, if that's possible - const playingState = Widget.Box({ // Wrap a box cuz overlay can't have margins itself + const playingState = Box({ // Wrap a box cuz overlay can't have margins itself homogeneous: true, - children: [Widget.Overlay({ - child: Widget.Box({ + children: [Overlay({ + child: Box({ vpack: 'center', className: 'bar-music-playstate', homogeneous: true, - children: [Widget.Label({ + children: [Label({ vpack: 'center', className: 'bar-music-playstate-txt', justification: 'center', @@ -121,9 +130,9 @@ export default () => { ] })] }); - const trackTitle = Widget.Scrollable({ + const trackTitle = Scrollable({ hexpand: true, - child: Widget.Label({ + child: Label({ className: 'txt-smallie txt-onSurfaceVariant', setup: (self) => self.hook(Mpris, label => { const mpris = Mpris.getPlayer(''); @@ -142,30 +151,57 @@ export default () => { trackTitle, ] }) - const systemResources = BarGroup({ - child: Box({ - children: [ - BarResource('RAM Usage', 'memory', `LANG=C free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`), - Revealer({ - revealChild: true, - transition: 'slide_left', - transitionDuration: 200, - child: Box({ - className: 'spacing-h-10 margin-left-10', - children: [ - BarResource('Swap Usage', 'swap_horiz', `LANG=C free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`), - BarResource('CPU Usage', 'settings_motion_mode', `LANG=C top -bn1 | grep Cpu | sed 's/\\,/\\./g' | awk '{print $2}'`), - ] - }), - setup: (self) => self.hook(Mpris, label => { - const mpris = Mpris.getPlayer(''); - self.revealChild = (!mpris); + const SystemResourcesOrCustomModule = () => { + // Check if ~/.cache/ags/user/scripts/custom-module-poll.sh exists + if (GLib.file_test(CUSTOM_MODULE_CONTENT_SCRIPT, GLib.FileTest.EXISTS)) { + const interval = Number(Utils.readFile(CUSTOM_MODULE_CONTENT_INTERVAL_FILE)) || 5000; + return BarGroup({ + child: Button({ + child: Label({ + className: 'txt-smallie txt-onSurfaceVariant', + useMarkup: true, + setup: (self) => Utils.timeout(1, () => { + self.label = exec(CUSTOM_MODULE_CONTENT_SCRIPT); + self.poll(interval, (self) => { + const content = exec(CUSTOM_MODULE_CONTENT_SCRIPT); + self.label = content; + }) + }) }), + onPrimaryClickRelease: () => execAsync(CUSTOM_MODULE_LEFTCLICK_SCRIPT).catch(print), + onSecondaryClickRelease: () => execAsync(CUSTOM_MODULE_RIGHTCLICK_SCRIPT).catch(print), + onMiddleClickRelease: () => execAsync(CUSTOM_MODULE_MIDDLECLICK_SCRIPT).catch(print), + onScrollUp: () => execAsync(CUSTOM_MODULE_SCROLLUP_SCRIPT).catch(print), + onScrollDown: () => execAsync(CUSTOM_MODULE_SCROLLDOWN_SCRIPT).catch(print), }) - ], - }) - }); - return Widget.EventBox({ + }); + } else { + return BarGroup({ + child: Box({ + children: [ + BarResource('RAM Usage', 'memory', `LANG=C free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`), + Revealer({ + revealChild: true, + transition: 'slide_left', + transitionDuration: 200, + child: Box({ + className: 'spacing-h-10 margin-left-10', + children: [ + BarResource('Swap Usage', 'swap_horiz', `LANG=C free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`), + BarResource('CPU Usage', 'settings_motion_mode', `LANG=C top -bn1 | grep Cpu | sed 's/\\,/\\./g' | awk '{print $2}'`), + ] + }), + setup: (self) => self.hook(Mpris, label => { + const mpris = Mpris.getPlayer(''); + self.revealChild = (!mpris); + }), + }) + ], + }) + }); + } + } + return EventBox({ onScrollUp: (self) => switchToRelativeWorkspace(self, -1), onScrollDown: (self) => switchToRelativeWorkspace(self, +1), onPrimaryClickRelease: () => showMusicControls.setValue(!showMusicControls.value), @@ -175,7 +211,7 @@ export default () => { className: 'spacing-h-5', children: [ BarGroup({ child: musicStuff }), - systemResources, + SystemResourcesOrCustomModule(), ] }) }); diff --git a/.config/ags/modules/bar/system.js b/.config/ags/modules/bar/system.js index 9a8fc86a8..5efdc0fdc 100644 --- a/.config/ags/modules/bar/system.js +++ b/.config/ags/modules/bar/system.js @@ -75,7 +75,7 @@ const Utilities = () => Box({ children: [ UtilButton({ name: 'Screen snip', icon: 'screenshot_region', onClicked: () => { - Utils.execAsync(['bash', '-c', `grim -g "$(slurp -d -c e2e2e2BB -b 31313122 -s 00000000)" - | wl-copy &`]) + Utils.execAsync(`${App.configDir}/scripts/grimblast.sh`) .catch(print) } }), diff --git a/.config/ags/modules/indicators/musiccontrols.js b/.config/ags/modules/indicators/musiccontrols.js index 4cf052135..6575c69ff 100644 --- a/.config/ags/modules/indicators/musiccontrols.js +++ b/.config/ags/modules/indicators/musiccontrols.js @@ -9,6 +9,8 @@ const { Box, EventBox, Icon, Scrollable, Label, Button, Revealer } = Widget; import { AnimatedCircProg } from "../.commonwidgets/cairo_circularprogress.js"; import { showMusicControls } from '../../variables.js'; +const COMPILED_STYLE_DIR = `${GLib.get_user_cache_dir()}/ags/user/generated` + function expandTilde(path) { if (path.startsWith('~')) { return GLib.get_home_dir() + path.slice(1); @@ -186,22 +188,19 @@ const CoverArt = ({ player, ...rest }) => { // Player closed // Note that cover path still remains, so we're checking title if (!player || player.trackTitle == "") { - self.css = `background-image: none;`; - App.applyCss(`${App.configDir}/style.css`); + App.applyCss(`${COMPILED_STYLE_DIR}/style.css`); return; } const coverPath = player.coverPath; const stylePath = `${player.coverPath}${lightDark}${COVER_COLORSCHEME_SUFFIX}`; if (player.coverPath == lastCoverPath) { // Since 'notify::cover-path' emits on cover download complete - // Utils.timeout(200, () => { self.css = `background-image: url('${coverPath}');`; }); Utils.timeout(200, () => self.attribute.showImage(self, coverPath)); } lastCoverPath = player.coverPath; // If a colorscheme has already been generated, skip generation if (fileExists(stylePath)) { - // Utils.timeout(200, () => { self.css = `background-image: url('${coverPath}');`; }); self.attribute.showImage(self, coverPath) App.applyCss(stylePath); return; @@ -213,8 +212,7 @@ const CoverArt = ({ player, ...rest }) => { .then(() => { exec(`wal -i "${player.coverPath}" -n -t -s -e -q ${lightDark}`) exec(`cp ${GLib.get_user_cache_dir()}/wal/colors.scss ${App.configDir}/scss/_musicwal.scss`); - exec(`sassc ${App.configDir}/scss/_music.scss ${stylePath}`); - // self.css = `background-image: url('${coverPath}');`; + exec(`sass ${App.configDir}/scss/_music.scss ${stylePath}`); Utils.timeout(200, () => self.attribute.showImage(self, coverPath)); App.applyCss(`${stylePath}`); }) diff --git a/.config/ags/modules/sideleft/apis/ai_chatmessage.js b/.config/ags/modules/sideleft/apis/ai_chatmessage.js index 5bc44f847..1c79b3a1c 100644 --- a/.config/ags/modules/sideleft/apis/ai_chatmessage.js +++ b/.config/ags/modules/sideleft/apis/ai_chatmessage.js @@ -3,16 +3,16 @@ import GtkSource from "gi://GtkSource?version=3.0"; import App from 'resource:///com/github/Aylur/ags/app.js'; import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; -const { Box, Button, Label, Scrollable } = Widget; +const { Box, Button, Label, Icon, Scrollable } = Widget; const { execAsync, exec } = Utils; import { MaterialIcon } from '../../.commonwidgets/materialicon.js'; import md2pango from '../../.miscutils/md2pango.js'; - +const LATEX_DIR = `${GLib.get_user_cache_dir()}/ags/media/latex`; const CUSTOM_SOURCEVIEW_SCHEME_PATH = `${App.configDir}/assets/themes/sourceviewtheme.xml`; const CUSTOM_SCHEME_ID = 'custom'; const USERNAME = GLib.get_user_name(); -const CHATGPT_CURSOR = ' ...'; +const AI_MESSAGE_CURSOR = ' ...'; /////////////////////// Custom source view colorscheme ///////////////////////// @@ -34,13 +34,6 @@ loadCustomColorScheme(CUSTOM_SOURCEVIEW_SCHEME_PATH); ////////////////////////////////////////////////////////////////////////////// -function copyToClipboard(text) { - const clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD); - const textVariant = new GLib.Variant('s', text); - clipboard.set_text(textVariant, -1); - clipboard.store(); -} - function substituteLang(str) { const subs = [ { from: 'javascript', to: 'js' }, @@ -82,7 +75,69 @@ const TextBlock = (content = '') => Label({ label: content, }); +Utils.execAsync(['bash', '-c', `rm ${LATEX_DIR}/*`]) + .then(() => Utils.execAsync(['bash', '-c', `mkdir -p ${LATEX_DIR}`])) + .catch(() => {}); +const Latex = (content = '') => { + const latexViewArea = Box({ + // vscroll: 'never', + // hscroll: 'automatic', + attribute: { + render: async (self, text) => { + if (text.length == 0) return; + const styleContext = self.get_style_context(); + const fontSize = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL); + + const timeSinceEpoch = Date.now(); + const fileName = `${timeSinceEpoch}.tex`; + const outFileName = `${timeSinceEpoch}-symbolic.svg`; + const scriptFileName = `${timeSinceEpoch}-render.sh`; + const filePath = `${LATEX_DIR}/${fileName}`; + const outFilePath = `${LATEX_DIR}/${outFileName}`; + const scriptFilePath = `${LATEX_DIR}/${scriptFileName}`; + + Utils.writeFile(text, filePath).catch(print); + // Since MicroTex doesn't support file path input properly, we gotta cat it + // And escaping such a command is a fucking pain so I decided to just generate a script + // Note: MicroTex doesn't support `&=` + // You can add this line in the middle for debugging: echo "$text" > ${filePath}.tmp + const renderScript = `#!/usr/bin/env bash +text=$(cat ${filePath} | sed 's/$/ \\\\\\\\/g' | sed 's/&=/=/g') +LaTeX -headless -input="$text" -output=${outFilePath} -textsize=${fontSize * 1.1} -padding=0 -maxwidth=${latexViewArea.get_allocated_width() * 0.85} +`; + Utils.writeFile(renderScript, scriptFilePath).catch(print); + Utils.exec(`chmod a+x ${scriptFilePath}`) + Utils.timeout(100, () => { + Utils.exec(`bash ${scriptFilePath}`); + Gtk.IconTheme.get_default().append_search_path(LATEX_DIR); + self.child?.destroy(); + self.child = Gtk.Image.new_from_file(outFilePath); + }) + } + }, + setup: (self) => self.attribute.render(self, content).catch(print), + }); + const wholeThing = Box({ + className: 'sidebar-chat-latex', + homogeneous: true, + attribute: { + 'updateText': (text) => { + latexViewArea.attribute.render(latexViewArea, text).catch(print); + } + }, + children: [Scrollable({ + vscroll: 'never', + hscroll: 'automatic', + child: latexViewArea + })] + }) + return wholeThing; +} + const CodeBlock = (content = '', lang = 'txt') => { + if (lang == 'tex' || lang == 'latex') { + return Latex(content); + } const topBar = Box({ className: 'sidebar-chat-codeblock-topbar', children: [ @@ -203,7 +258,7 @@ const MessageContent = (content) => { const lastLabel = kids[kids.length - 1]; let blockContent = lines.slice(lastProcessed, lines.length).join('\n'); if (!inCode) - lastLabel.label = `${md2pango(blockContent)}${useCursor ? CHATGPT_CURSOR : ''}`; + lastLabel.label = `${md2pango(blockContent)}${useCursor ? AI_MESSAGE_CURSOR : ''}`; else lastLabel.attribute.updateText(blockContent); } diff --git a/.config/ags/modules/sideleft/apis/chatgpt.js b/.config/ags/modules/sideleft/apis/chatgpt.js index b4ffaf869..c3e657aab 100644 --- a/.config/ags/modules/sideleft/apis/chatgpt.js +++ b/.config/ags/modules/sideleft/apis/chatgpt.js @@ -4,12 +4,13 @@ import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; const { Box, Button, Icon, Label, Revealer, Scrollable } = Widget; -import ChatGPT from '../../../services/chatgpt.js'; +import GPTService from '../../../services/gpt.js'; import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js'; import { SystemMessage, ChatMessage } from "./ai_chatmessage.js"; import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../.commonwidgets/configwidgets.js'; import { markdownTest } from '../../.miscutils/md2pango.js'; import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js'; +import { MaterialIcon } from '../../.commonwidgets/materialicon.js'; Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets/icons`); @@ -19,7 +20,95 @@ export const chatGPTTabIcon = Icon({ icon: `openai-symbolic`, }); -const ChatGPTInfo = () => { +const ProviderSwitcher = () => { + const ProviderChoice = (id, provider) => { + const providerSelected = MaterialIcon('check', 'norm', { + setup: (self) => self.hook(GPTService, (self) => { + self.toggleClassName('invisible', GPTService.providerID !== id); + }, 'providerChanged') + }); + return Button({ + tooltipText: provider.description, + onClicked: () => { + GPTService.providerID = id; + providerList.revealChild = false; + indicatorChevron.label = 'expand_more'; + }, + child: Box({ + className: 'spacing-h-10 txt', + children: [ + Icon({ + icon: provider['logo_name'], + className: 'txt-large' + }), + Label({ + hexpand: true, + xalign: 0, + className: 'txt-small', + label: provider.name, + }), + providerSelected + ], + }), + setup: setupCursorHover, + }); + } + const indicatorChevron = MaterialIcon('expand_more', 'norm'); + const indicatorButton = Button({ + tooltipText: 'Select ChatGPT-compatible API provider', + child: Box({ + className: 'spacing-h-10 txt', + children: [ + MaterialIcon('cloud', 'norm'), + Label({ + hexpand: true, + xalign: 0, + className: 'txt-small', + label: GPTService.providerID, + setup: (self) => self.hook(GPTService, (self) => { + self.label = `${GPTService.providers[GPTService.providerID]['name']}`; + }, 'providerChanged') + }), + indicatorChevron, + ] + }), + onClicked: () => { + providerList.revealChild = !providerList.revealChild; + indicatorChevron.label = (providerList.revealChild ? 'expand_less' : 'expand_more'); + }, + setup: setupCursorHover, + }); + const providerList = Revealer({ + revealChild: false, + transition: 'slide_down', + transitionDuration: 180, + child: Box({ + vertical: true, className: 'spacing-v-5 sidebar-chat-providerswitcher-list', + children: [ + Box({ className: 'separator-line margin-top-5 margin-bottom-5' }), + Box({ + className: 'spacing-v-5', + vertical: true, + setup: (self) => self.hook(GPTService, (self) => { + self.children = Object.entries(GPTService.providers) + .map(([id, provider]) => ProviderChoice(id, provider)); + }, 'initialized'), + }) + ] + }) + }) + return Box({ + hpack: 'center', + vertical: true, + className: 'sidebar-chat-providerswitcher', + children: [ + indicatorButton, + providerList, + ] + }) +} + +const GPTInfo = () => { const openAiLogo = Icon({ hpack: 'center', className: 'sidebar-chat-welcome-logo', @@ -34,7 +123,7 @@ const ChatGPTInfo = () => { className: 'txt txt-title-small sidebar-chat-welcome-txt', wrap: true, justify: Gtk.Justification.CENTER, - label: 'Assistant (ChatGPT 3.5)', + label: 'Assistant (GPTs)', }), Box({ className: 'spacing-h-5', @@ -49,7 +138,7 @@ const ChatGPTInfo = () => { Button({ className: 'txt-subtext txt-norm icon-material', label: 'info', - tooltipText: 'Uses gpt-3.5-turbo.\nNot affiliated, endorsed, or sponsored by OpenAI.', + tooltipText: 'Uses gpt-3.5-turbo.\nNot affiliated, endorsed, or sponsored by OpenAI.\n\nPrivacy: OpenAI claims they do not use your data when you use their API.', setup: setupCursorHoverInfo, }), ] @@ -58,14 +147,14 @@ const ChatGPTInfo = () => { }); } -export const ChatGPTSettings = () => MarginRevealer({ +const GPTSettings = () => MarginRevealer({ transition: 'slide_down', revealChild: true, extraSetup: (self) => self - .hook(ChatGPT, (self) => Utils.timeout(200, () => { + .hook(GPTService, (self) => Utils.timeout(200, () => { self.attribute.hide(); }), 'newMsg') - .hook(ChatGPT, (self) => Utils.timeout(200, () => { + .hook(GPTService, (self) => Utils.timeout(200, () => { self.attribute.show(); }), 'clear') , @@ -77,7 +166,7 @@ export const ChatGPTSettings = () => MarginRevealer({ hpack: 'center', icon: 'casino', name: 'Randomness', - desc: 'ChatGPT\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1', + desc: 'The model\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1', options: [ { value: 0.00, name: 'Precise', }, { value: 0.50, name: 'Balanced', }, @@ -85,7 +174,7 @@ export const ChatGPTSettings = () => MarginRevealer({ ], initIndex: 2, onChange: (value, name) => { - ChatGPT.temperature = value; + GPTService.temperature = value; }, }), ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15 @@ -98,18 +187,18 @@ export const ChatGPTSettings = () => MarginRevealer({ icon: 'cycle', name: 'Cycle models', desc: 'Helps avoid exceeding the API rate of 3 messages per minute.\nTurn this on if you message rapidly.', - initValue: ChatGPT.cycleModels, + initValue: GPTService.cycleModels, onChange: (self, newValue) => { - ChatGPT.cycleModels = newValue; + GPTService.cycleModels = newValue; }, }), ConfigToggle({ icon: 'model_training', name: 'Enhancements', - desc: 'Tells ChatGPT:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points', - initValue: ChatGPT.assistantPrompt, + desc: 'Tells the model:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points', + initValue: GPTService.assistantPrompt, onChange: (self, newValue) => { - ChatGPT.assistantPrompt = newValue; + GPTService.assistantPrompt = newValue; }, }), ] @@ -124,8 +213,8 @@ export const OpenaiApiKeyInstructions = () => Box({ transition: 'slide_down', transitionDuration: 150, setup: (self) => self - .hook(ChatGPT, (self, hasKey) => { - self.revealChild = (ChatGPT.key.length == 0); + .hook(GPTService, (self, hasKey) => { + self.revealChild = (GPTService.key.length == 0); }, 'hasKey') , child: Button({ @@ -134,17 +223,17 @@ export const OpenaiApiKeyInstructions = () => Box({ wrap: true, className: 'txt sidebar-chat-welcome-txt', justify: Gtk.Justification.CENTER, - label: 'An OpenAI API key is required\nYou can grab one here, then enter it below' + label: 'An API key is required\nYou can grab one here, then enter it below' }), setup: setupCursorHover, onClicked: () => { - Utils.execAsync(['bash', '-c', `xdg-open https://platform.openai.com/api-keys &`]); + Utils.execAsync(['bash', '-c', `xdg-open ${GPTService.getKeyUrl}`]); } }) })] }); -const chatGPTWelcome = Box({ +const GPTWelcome = () => Box({ vexpand: true, homogeneous: true, child: Box({ @@ -152,9 +241,9 @@ const chatGPTWelcome = Box({ vpack: 'center', vertical: true, children: [ - ChatGPTInfo(), + GPTInfo(), OpenaiApiKeyInstructions(), - ChatGPTSettings(), + GPTSettings(), ] }) }); @@ -163,16 +252,16 @@ export const chatContent = Box({ className: 'spacing-v-15', vertical: true, setup: (self) => self - .hook(ChatGPT, (box, id) => { - const message = ChatGPT.messages[id]; + .hook(GPTService, (box, id) => { + const message = GPTService.messages[id]; if (!message) return; - box.add(ChatMessage(message, 'ChatGPT')) + box.add(ChatMessage(message, `Model (${GPTService.providers[GPTService.providerID]['name']})`)) }, 'newMsg') , }); const clearChat = () => { - ChatGPT.clear(); + GPTService.clear(); const children = chatContent.get_children(); for (let i = 0; i < children.length; i++) { const child = children[i]; @@ -180,34 +269,6 @@ const clearChat = () => { } } -export const chatGPTView = Scrollable({ - className: 'sidebar-chat-viewport', - vexpand: true, - child: Box({ - vertical: true, - children: [ - chatGPTWelcome, - chatContent, - ] - }), - setup: (scrolledWindow) => { - // Show scrollbar - scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); - const vScrollbar = scrolledWindow.get_vscrollbar(); - vScrollbar.get_style_context().add_class('sidebar-scrollbar'); - // Avoid click-to-scroll-widget-to-view behavior - Utils.timeout(1, () => { - const viewport = scrolledWindow.child; - viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined)); - }) - // Always scroll to bottom with new content - const adjustment = scrolledWindow.get_vadjustment(); - adjustment.connect("changed", () => { - adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size()); - }) - } -}); - const CommandButton = (command) => Button({ className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small', onClicked: () => sendMessage(command), @@ -228,16 +289,16 @@ export const chatGPTCommands = Box({ export const sendMessage = (text) => { // Check if text or API key is empty if (text.length == 0) return; - if (ChatGPT.key.length == 0) { - ChatGPT.key = text; - chatContent.add(SystemMessage(`Key saved to\n\`${ChatGPT.keyPath}\``, 'API Key', chatGPTView)); + if (GPTService.key.length == 0) { + GPTService.key = text; + chatContent.add(SystemMessage(`Key saved to\n\`${GPTService.keyPath}\``, 'API Key', chatGPTView)); text = ''; return; } // Commands if (text.startsWith('/')) { if (text.startsWith('/clear')) clearChat(); - else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${ChatGPT.modelName}\``, '/model', chatGPTView)) + else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${GPTService.modelName}\``, '/model', chatGPTView)) else if (text.startsWith('/prompt')) { const firstSpaceIndex = text.indexOf(' '); const prompt = text.slice(firstSpaceIndex + 1); @@ -245,18 +306,18 @@ export const sendMessage = (text) => { chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', chatGPTView)) } else { - ChatGPT.addMessage('user', prompt) + GPTService.addMessage('user', prompt) } } else if (text.startsWith('/key')) { const parts = text.split(' '); if (parts.length == 1) chatContent.add(SystemMessage( - `Key stored in:\n\`${ChatGPT.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``, + `Key stored in:\n\`${GPTService.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``, '/key', chatGPTView)); else { - ChatGPT.key = parts[1]; - chatContent.add(SystemMessage(`Updated API Key at\n\`${ChatGPT.keyPath}\``, '/key', chatGPTView)); + GPTService.key = parts[1]; + chatContent.add(SystemMessage(`Updated API Key at\n\`${GPTService.keyPath}\``, '/key', chatGPTView)); } } else if (text.startsWith('/test')) @@ -265,6 +326,40 @@ export const sendMessage = (text) => { chatContent.add(SystemMessage(`Invalid command.`, 'Error', chatGPTView)) } else { - ChatGPT.send(text); + GPTService.send(text); } -} \ No newline at end of file +} + +export const chatGPTView = Box({ + vertical: true, + children: [ + ProviderSwitcher(), + Scrollable({ + className: 'sidebar-chat-viewport', + vexpand: true, + child: Box({ + vertical: true, + children: [ + GPTWelcome(), + chatContent, + ] + }), + setup: (scrolledWindow) => { + // Show scrollbar + scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); + const vScrollbar = scrolledWindow.get_vscrollbar(); + vScrollbar.get_style_context().add_class('sidebar-scrollbar'); + // Avoid click-to-scroll-widget-to-view behavior + Utils.timeout(1, () => { + const viewport = scrolledWindow.child; + viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined)); + }) + // Always scroll to bottom with new content + const adjustment = scrolledWindow.get_vadjustment(); + adjustment.connect("changed", () => { + adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size()); + }) + } + }) + ] +}); \ No newline at end of file diff --git a/.config/ags/modules/sideleft/apis/gemini.js b/.config/ags/modules/sideleft/apis/gemini.js index 7f2e4e107..7da16e435 100644 --- a/.config/ags/modules/sideleft/apis/gemini.js +++ b/.config/ags/modules/sideleft/apis/gemini.js @@ -4,7 +4,7 @@ import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; const { Box, Button, Icon, Label, Revealer, Scrollable } = Widget; -import Gemini from '../../../services/gemini.js'; +import GeminiService from '../../../services/gemini.js'; import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js'; import { SystemMessage, ChatMessage } from "./ai_chatmessage.js"; import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../.commonwidgets/configwidgets.js'; @@ -50,7 +50,7 @@ const GeminiInfo = () => { Button({ className: 'txt-subtext txt-norm icon-material', label: 'info', - tooltipText: 'Uses gemini-pro.\nNot affiliated, endorsed, or sponsored by Google.', + tooltipText: 'Uses gemini-pro.\nNot affiliated, endorsed, or sponsored by Google.\n\nPrivacy: Google collects data for training by default.\nIf you mind, turn off Gemini Apps Activity in your account.', setup: setupCursorHoverInfo, }), ] @@ -63,10 +63,10 @@ export const GeminiSettings = () => MarginRevealer({ transition: 'slide_down', revealChild: true, extraSetup: (self) => self - .hook(Gemini, (self) => Utils.timeout(200, () => { + .hook(GeminiService, (self) => Utils.timeout(200, () => { self.attribute.hide(); }), 'newMsg') - .hook(Gemini, (self) => Utils.timeout(200, () => { + .hook(GeminiService, (self) => Utils.timeout(200, () => { self.attribute.show(); }), 'clear') , @@ -86,7 +86,7 @@ export const GeminiSettings = () => MarginRevealer({ ], initIndex: 2, onChange: (value, name) => { - Gemini.temperature = value; + GeminiService.temperature = value; }, }), ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15 @@ -99,9 +99,9 @@ export const GeminiSettings = () => MarginRevealer({ icon: 'model_training', name: 'Enhancements', desc: 'Tells Gemini:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points', - initValue: Gemini.assistantPrompt, + initValue: GeminiService.assistantPrompt, onChange: (self, newValue) => { - Gemini.assistantPrompt = newValue; + GeminiService.assistantPrompt = newValue; }, }), ] @@ -116,8 +116,8 @@ export const GoogleAiInstructions = () => Box({ transition: 'slide_down', transitionDuration: 150, setup: (self) => self - .hook(Gemini, (self, hasKey) => { - self.revealChild = (Gemini.key.length == 0); + .hook(GeminiService, (self, hasKey) => { + self.revealChild = (GeminiService.key.length == 0); }, 'hasKey') , child: Button({ @@ -155,8 +155,8 @@ export const chatContent = Box({ className: 'spacing-v-15', vertical: true, setup: (self) => self - .hook(Gemini, (box, id) => { - const message = Gemini.messages[id]; + .hook(GeminiService, (box, id) => { + const message = GeminiService.messages[id]; if (!message) return; box.add(ChatMessage(message, MODEL_NAME)) }, 'newMsg') @@ -164,7 +164,7 @@ export const chatContent = Box({ }); const clearChat = () => { - Gemini.clear(); + GeminiService.clear(); const children = chatContent.get_children(); for (let i = 0; i < children.length; i++) { const child = children[i]; @@ -172,34 +172,6 @@ const clearChat = () => { } } -export const geminiView = Scrollable({ - className: 'sidebar-chat-viewport', - vexpand: true, - child: Box({ - vertical: true, - children: [ - geminiWelcome, - chatContent, - ] - }), - setup: (scrolledWindow) => { - // Show scrollbar - scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); - const vScrollbar = scrolledWindow.get_vscrollbar(); - vScrollbar.get_style_context().add_class('sidebar-scrollbar'); - // Avoid click-to-scroll-widget-to-view behavior - Utils.timeout(1, () => { - const viewport = scrolledWindow.child; - viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined)); - }) - // Always scroll to bottom with new content - const adjustment = scrolledWindow.get_vadjustment(); - adjustment.connect("changed", () => { - adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size()); - }) - } -}); - const CommandButton = (command) => Button({ className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small', onClicked: () => sendMessage(command), @@ -220,16 +192,16 @@ export const geminiCommands = Box({ export const sendMessage = (text) => { // Check if text or API key is empty if (text.length == 0) return; - if (Gemini.key.length == 0) { - Gemini.key = text; - chatContent.add(SystemMessage(`Key saved to\n\`${Gemini.keyPath}\``, 'API Key', geminiView)); + if (GeminiService.key.length == 0) { + GeminiService.key = text; + chatContent.add(SystemMessage(`Key saved to\n\`${GeminiService.keyPath}\``, 'API Key', geminiView)); text = ''; return; } // Commands if (text.startsWith('/')) { if (text.startsWith('/clear')) clearChat(); - else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${Gemini.modelName}\``, '/model', geminiView)) + else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${GeminiService.modelName}\``, '/model', geminiView)) else if (text.startsWith('/prompt')) { const firstSpaceIndex = text.indexOf(' '); const prompt = text.slice(firstSpaceIndex + 1); @@ -237,18 +209,18 @@ export const sendMessage = (text) => { chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', geminiView)) } else { - Gemini.addMessage('user', prompt) + GeminiService.addMessage('user', prompt) } } else if (text.startsWith('/key')) { const parts = text.split(' '); if (parts.length == 1) chatContent.add(SystemMessage( - `Key stored in:\n\`${Gemini.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``, + `Key stored in:\n\`${GeminiService.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``, '/key', geminiView)); else { - Gemini.key = parts[1]; - chatContent.add(SystemMessage(`Updated API Key at\n\`${Gemini.keyPath}\``, '/key', geminiView)); + GeminiService.key = parts[1]; + chatContent.add(SystemMessage(`Updated API Key at\n\`${GeminiService.keyPath}\``, '/key', geminiView)); } } else if (text.startsWith('/test')) @@ -257,6 +229,37 @@ export const sendMessage = (text) => { chatContent.add(SystemMessage(`Invalid command.`, 'Error', geminiView)) } else { - Gemini.send(text); + GeminiService.send(text); } -} \ No newline at end of file +} + +export const geminiView = Box({ + homogeneous: true, + children: [Scrollable({ + className: 'sidebar-chat-viewport', + vexpand: true, + child: Box({ + vertical: true, + children: [ + geminiWelcome, + chatContent, + ] + }), + setup: (scrolledWindow) => { + // Show scrollbar + scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); + const vScrollbar = scrolledWindow.get_vscrollbar(); + vScrollbar.get_style_context().add_class('sidebar-scrollbar'); + // Avoid click-to-scroll-widget-to-view behavior + Utils.timeout(1, () => { + const viewport = scrolledWindow.child; + viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined)); + }) + // Always scroll to bottom with new content + const adjustment = scrolledWindow.get_vadjustment(); + adjustment.connect("changed", () => { + adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size()); + }) + } + })] +}); \ No newline at end of file diff --git a/.config/ags/modules/sideleft/apiwidgets.js b/.config/ags/modules/sideleft/apiwidgets.js index c141e32ca..ac89b6a66 100644 --- a/.config/ags/modules/sideleft/apiwidgets.js +++ b/.config/ags/modules/sideleft/apiwidgets.js @@ -6,7 +6,7 @@ const { execAsync, exec } = Utils; import { setupCursorHover, setupCursorHoverInfo } from '../.widgetutils/cursorhover.js'; import { contentStack } from './sideleft.js'; // APIs -import ChatGPT from '../../services/chatgpt.js'; +import GPTService from '../../services/gpt.js'; import Gemini from '../../services/gemini.js'; import { geminiView, geminiCommands, sendMessage as geminiSendMessage, geminiTabIcon } from './apis/gemini.js'; import { chatGPTView, chatGPTCommands, sendMessage as chatGPTSendMessage, chatGPTTabIcon } from './apis/chatgpt.js'; @@ -26,12 +26,12 @@ const APIS = [ placeholderText: 'Message Gemini...', }, { - name: 'Assistant (ChatGPT 3.5)', + name: 'Assistant (GPTs)', sendCommand: chatGPTSendMessage, contentWidget: chatGPTView, commandBar: chatGPTCommands, tabIcon: chatGPTTabIcon, - placeholderText: 'Message ChatGPT...', + placeholderText: 'Message the model...', }, { name: 'Waifus', @@ -65,9 +65,9 @@ export const chatEntry = TextView({ acceptsTab: false, className: 'sidebar-chat-entry txt txt-smallie', setup: (self) => self - .hook(ChatGPT, (self) => { - if (APIS[currentApiId].name != 'Assistant (ChatGPT 3.5)') return; - self.placeholderText = (ChatGPT.key.length > 0 ? 'Message ChatGPT...' : 'Enter OpenAI API Key...'); + .hook(GPTService, (self) => { + if (APIS[currentApiId].name != 'Assistant (GPTs)') return; + self.placeholderText = (GPTService.key.length > 0 ? 'Message the model...' : 'Enter API Key...'); }, 'hasKey') .hook(Gemini, (self) => { if (APIS[currentApiId].name != 'Assistant (Gemini Pro)') return; diff --git a/.config/ags/scripts/color_generation/applycolor.sh b/.config/ags/scripts/color_generation/applycolor.sh index d6d16b504..a8304abe8 100755 --- a/.config/ags/scripts/color_generation/applycolor.sh +++ b/.config/ags/scripts/color_generation/applycolor.sh @@ -66,7 +66,7 @@ apply_gtklock() { # Copy template mkdir -p "$HOME"/.cache/ags/user/generated/gtklock - sassc "scripts/templates/gtklock/main.scss" "$HOME"/.cache/ags/user/generated/gtklock/style.css + sass "scripts/templates/gtklock/main.scss" "$HOME"/.cache/ags/user/generated/gtklock/style.css cp "$HOME"/.cache/ags/user/generated/gtklock/style.css "$HOME"/.config/gtklock/style.css } @@ -195,9 +195,9 @@ apply_gtk() { # Using gradience-cli } apply_ags() { - sassc "$HOME"/.config/ags/scss/main.scss "$HOME"/.config/ags/style.css + sass "$HOME"/.config/ags/scss/main.scss "$HOME"/.cache/ags/user/generated/style.css ags run-js 'openColorScheme.value = true; Utils.timeout(2000, () => openColorScheme.value = false);' - ags run-js "App.resetCss(); App.applyCss('${HOME}/.config/ags/style.css');" + ags run-js "App.resetCss(); App.applyCss('${HOME}/.cache/ags/user/generated/style.css');" } apply_ags & diff --git a/.config/ags/scripts/color_generation/colorgen.sh b/.config/ags/scripts/color_generation/colorgen.sh index 93a7e7504..1748ffb9d 100755 --- a/.config/ags/scripts/color_generation/colorgen.sh +++ b/.config/ags/scripts/color_generation/colorgen.sh @@ -42,7 +42,7 @@ elif [ "$backend" = "pywal" ]; then cat color_generation/pywal_to_material.scss >> "$HOME"/.cache/ags/user/generated/material_colors.scss if [ "$2" = "--apply" ]; then - sassc "$HOME"/.cache/ags/user/generated/material_colors.scss "$HOME"/.cache/ags/user/generated/colors_classes.scss --style compact + sass "$HOME"/.cache/ags/user/generated/material_colors.scss "$HOME"/.cache/ags/user/generated/colors_classes.scss --style compact sed -i "s/ { color//g" "$HOME"/.cache/ags/user/generated/colors_classes.scss sed -i "s/\./$/g" "$HOME"/.cache/ags/user/generated/colors_classes.scss sed -i "s/}//g" "$HOME"/.cache/ags/user/generated/colors_classes.scss diff --git a/.config/ags/scripts/grimblast.sh b/.config/ags/scripts/grimblast.sh new file mode 100755 index 000000000..db8bb5069 --- /dev/null +++ b/.config/ags/scripts/grimblast.sh @@ -0,0 +1,278 @@ +#!/usr/bin/env bash +## Grimblast: a helper for screenshots within hyprland +## Requirements: +## - `grim`: screenshot utility for wayland +## - `slurp`: to select an area +## - `hyprctl`: to read properties of current window (provided by Hyprland) +## - `hyprpicker`: to freeze the screen when selecting area +## - `wl-copy`: clipboard utility (provided by wl-clipboard) +## - `jq`: json utility to parse hyprctl output +## - `notify-send`: to show notifications (provided by libnotify) +## Those are needed to be installed, if unsure, run `grimblast check` +## +## See `man 1 grimblast` or `grimblast usage` for further details. + +## Author: Misterio (https://github.com/misterio77) + +## This tool is based on grimshot, with swaymsg commands replaced by their +## hyprctl equivalents. +## https://github.com/swaywm/sway/blob/master/contrib/grimshot +jq=gojq +getTargetDirectory() { + test -f "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" && + . "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" + + echo "${XDG_SCREENSHOTS_DIR:-${XDG_PICTURES_DIR:-$HOME}}" +} + +tmp_editor_directory() { + echo "/tmp" +} + +#Detect if $GRIMBLAST_EDITOR env exist +env_editor_confirm() { + if [ -n "$GRIMBLAST_EDITOR" ]; then + echo "GRIMBLAST_EDITOR is set. Continuing..." + else + echo "GRIMBLAST_EDITOR is not set. Defaulting to gimp" + GRIMBLAST_EDITOR=gimp + fi +} + +NOTIFY=no +CURSOR= +FREEZE= +WAIT=no +SCALE= +HYPRPICKER_PID=-1 + +while [ $# -gt 0 ]; do + key="$1" + + case $key in + -n | --notify) + NOTIFY=yes + shift # past argument + ;; + -c | --cursor) + CURSOR=yes + shift # past argument + ;; + -f | --freeze) + FREEZE=yes + shift # past argument + ;; + -w | --wait) + shift + WAIT=$1 + if echo "$WAIT" | grep "[^0-9]" -q; then + echo "Invalid value for wait '$WAIT'" >&2 + exit 3 + fi + shift + ;; + -s | --scale) + shift # past argument + if [ $# -gt 0 ]; then + SCALE="$1" # assign the next argument to SCALE + shift # past argument + else + echo "Error: Missing argument for --scale option." + exit 1 + fi + ;; + *) # unknown option + break # done with parsing --flags + ;; + esac +done + +ACTION=${1:-usage} +SUBJECT=${2:-screen} +FILE=${3:-$(getTargetDirectory)/$(date -Ins).png} +FILE_EDITOR=${3:-$(tmp_editor_directory)/$(date -Ins).png} + +if [ "$ACTION" != "save" ] && [ "$ACTION" != "copy" ] && [ "$ACTION" != "edit" ] && [ "$ACTION" != "copysave" ] && [ "$ACTION" != "check" ]; then + echo "Usage:" + echo " grimblast [--notify] [--cursor] [--freeze] [--wait N] [--scale ] (copy|save|copysave|edit) [active|screen|output|area] [FILE|-]" + echo " grimblast check" + echo " grimblast usage" + echo "" + echo "Commands:" + echo " copy: Copy the screenshot data into the clipboard." + echo " save: Save the screenshot to a regular file or '-' to pipe to STDOUT." + echo " copysave: Combine the previous 2 options." + echo " edit: Open screenshot in the image editor of your choice (default is gimp). See man page for info." + echo " check: Verify if required tools are installed and exit." + echo " usage: Show this message and exit." + echo "" + echo "Targets:" + echo " active: Currently active window." + echo " screen: All visible outputs." + echo " output: Currently active output." + echo " area: Manually select a region or window." + exit +fi + +notify() { + notify-send -t 3000 -a grimblast "$@" +} + +notifyOk() { + [ "$NOTIFY" = "no" ] && return + + notify "$@" +} + +notifyError() { + if [ $NOTIFY = "yes" ]; then + TITLE=${2:-"Screenshot"} + MESSAGE=${1:-"Error taking screenshot with grim"} + notify -u critical "$TITLE" "$MESSAGE" + else + echo "$1" + fi +} + +resetFade() { + if [[ -n $FADE && -n $FADEOUT ]]; then + hyprctl keyword animation "$FADE" >/dev/null + hyprctl keyword animation "$FADEOUT" >/dev/null + fi +} + +killHyprpicker() { + if [ ! $HYPRPICKER_PID -eq -1 ]; then + kill $HYPRPICKER_PID + fi +} + +die() { + killHyprpicker + MSG=${1:-Bye} + notifyError "Error: $MSG" + exit 2 +} + +check() { + COMMAND=$1 + if command -v "$COMMAND" >/dev/null 2>&1; then + RESULT="OK" + else + RESULT="NOT FOUND" + fi + echo " $COMMAND: $RESULT" +} + +takeScreenshot() { + FILE=$1 + GEOM=$2 + OUTPUT=$3 + if [ -n "$OUTPUT" ]; then + grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} -o "$OUTPUT" "$FILE" || die "Unable to invoke grim" + elif [ -z "$GEOM" ]; then + grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} "$FILE" || die "Unable to invoke grim" + else + grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} -g "$GEOM" "$FILE" || die "Unable to invoke grim" + resetFade + fi +} + +wait() { + if [ "$WAIT" != "no" ]; then + sleep "$WAIT" + fi +} + +if [ "$ACTION" = "check" ]; then + echo "Checking if required tools are installed. If something is missing, install it to your system and make it available in PATH..." + check grim + check slurp + check hyprctl + check hyprpicker + check wl-copy + check $jq + check notify-send + exit +elif [ "$SUBJECT" = "active" ]; then + wait + FOCUSED=$(hyprctl activewindow -j) + GEOM=$(echo "$FOCUSED" | $jq -r '"\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"') + APP_ID=$(echo "$FOCUSED" | $jq -r '.class') + WHAT="$APP_ID window" +elif [ "$SUBJECT" = "screen" ]; then + wait + GEOM="" + WHAT="Screen" +elif [ "$SUBJECT" = "output" ]; then + wait + GEOM="" + OUTPUT=$(hyprctl monitors -j | $jq -r '.[] | select(.focused == true)' | $jq -r '.name') + WHAT="$OUTPUT" +elif [ "$SUBJECT" = "area" ]; then + if [ "$FREEZE" = "yes" ] && [ "$(command -v "hyprpicker")" ] >/dev/null 2>&1; then + hyprpicker -r -z & + sleep 0.2 + HYPRPICKER_PID=$! + fi + + # get fade & fadeOut animation and unset it + # this removes the black border seen around screenshots + FADE="$(hyprctl -j animations | $jq -jr '.[0][] | select(.name == "fade") | .name, ",", (if .enabled == true then "1" else "0" end), ",", (.speed|floor), ",", .bezier')" + FADEOUT="$(hyprctl -j animations | $jq -jr '.[0][] | select(.name == "fadeOut") | .name, ",", (if .enabled == true then "1" else "0" end), ",", (.speed|floor), ",", .bezier')" + hyprctl keyword animation 'fade,0,1,default' >/dev/null + hyprctl keyword animation 'fadeOut,0,1,default' >/dev/null + + WORKSPACES="$(hyprctl monitors -j | $jq -r 'map(.activeWorkspace.id)')" + WINDOWS="$(hyprctl clients -j | $jq -r --argjson workspaces "$WORKSPACES" 'map(select([.workspace.id] | inside($workspaces)))')" + # shellcheck disable=2086 # if we don't split, spaces mess up slurp + GEOM=$(echo "$WINDOWS" | $jq -r '.[] | "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"' | slurp $SLURP_ARGS) + + # Check if user exited slurp without selecting the area + if [ -z "$GEOM" ]; then + killHyprpicker + resetFade + exit 1 + fi + WHAT="Area" + wait +elif [ "$SUBJECT" = "window" ]; then + die "Subject 'window' is now included in 'area'" +else + die "Unknown subject to take a screen shot from" "$SUBJECT" +fi + +if [ "$ACTION" = "copy" ]; then + takeScreenshot - "$GEOM" "$OUTPUT" | wl-copy --type image/png || die "Clipboard error" + notifyOk "$WHAT copied to buffer" +elif [ "$ACTION" = "save" ]; then + if takeScreenshot "$FILE" "$GEOM" "$OUTPUT"; then + TITLE="Screenshot of $SUBJECT" + MESSAGE=$(basename "$FILE") + notifyOk "$TITLE" "$MESSAGE" -i "$FILE" + echo "$FILE" + else + notifyError "Error taking screenshot with grim" + fi +elif [ "$ACTION" = "edit" ]; then + env_editor_confirm + if takeScreenshot "$FILE_EDITOR" "$GEOM" "$OUTPUT"; then + TITLE="Screenshot of $SUBJECT" + MESSAGE="Open screenshot in image editor" + notifyOk "$TITLE" "$MESSAGE" -i "$FILE_EDITOR" + $GRIMBLAST_EDITOR "$FILE_EDITOR" + echo "$FILE_EDITOR" + else + notifyError "Error taking screenshot" + fi +else + if [ "$ACTION" = "copysave" ]; then + takeScreenshot - "$GEOM" "$OUTPUT" | tee "$FILE" | wl-copy --type image/png || die "Clipboard error" + notifyOk "$WHAT copied to buffer and saved to $FILE" -i "$FILE" + echo "$FILE" + else + notifyError "Error taking screenshot with grim" + fi +fi + +killHyprpicker diff --git a/.config/ags/scripts/templates/hypr/hyprland/colors.conf b/.config/ags/scripts/templates/hypr/hyprland/colors.conf index 3a0bf4a34..8c0689f31 100644 --- a/.config/ags/scripts/templates/hypr/hyprland/colors.conf +++ b/.config/ags/scripts/templates/hypr/hyprland/colors.conf @@ -1,4 +1,4 @@ -$SLURP_COMMAND="$(slurp -d -c {{ $onSecondaryContainer }}BB -b {{ $secondaryContainer }}44 -s 00000000)" +# exec = export SLURP_ARGS='-d -c {{ $onSecondaryContainer }}BB -b {{ $secondaryContainer }}44 -s 00000000' general { col.active_border = rgba({{ $onSurface }}39) diff --git a/.config/ags/scripts/templates/hypr/hyprlock.conf b/.config/ags/scripts/templates/hypr/hyprlock.conf index 3ad0f0d6f..5a42af563 100644 --- a/.config/ags/scripts/templates/hypr/hyprlock.conf +++ b/.config/ags/scripts/templates/hypr/hyprlock.conf @@ -40,7 +40,7 @@ label { # Clock halign = center valign = center } -label { +label { # Greeting monitor = text = hi $USER !!! color = $text_color @@ -51,7 +51,7 @@ label { halign = center valign = center } -label { +label { # lock icon monitor = text = lock color = $text_color @@ -62,7 +62,7 @@ label { halign = center valign = bottom } -label { +label { # "locked" text monitor = text = locked color = $text_color @@ -72,4 +72,16 @@ label { position = 0, 50 halign = center valign = bottom +} + +label { # Status + monitor = + text = cmd[update:5000] ~/.config/hypr/hyprlock/status.sh + color = $text_color + font_size = 14 + font_family = $font_family + + position = 30, -30 + halign = left + valign = top } \ No newline at end of file diff --git a/.config/ags/scss/_lib_classes.scss b/.config/ags/scss/_lib_classes.scss index 6cd3f893b..988dee7a2 100644 --- a/.config/ags/scss/_lib_classes.scss +++ b/.config/ags/scss/_lib_classes.scss @@ -167,7 +167,7 @@ } .separator-line { - background-color: $outline; + background-color: mix($subtext, $surface, 50%); min-width: 0.068rem; min-height: 0.068rem; } diff --git a/.config/ags/scss/_osk.scss b/.config/ags/scss/_osk.scss index eca128c76..d5e62fffd 100644 --- a/.config/ags/scss/_osk.scss +++ b/.config/ags/scss/_osk.scss @@ -69,7 +69,8 @@ $osk_key_fontsize: 1.091rem; .osk-key-fn { min-width: $osk_key_width * 1.005; - min-height: $osk_key_height / 2; + min-height: calc($osk_key_height / 2); // dart-sass + // min-height: $osk_key_height / 2; // sassc } .osk-key-tab { diff --git a/.config/ags/scss/_sidebars.scss b/.config/ags/scss/_sidebars.scss index 51a3b5dde..1989c9cb8 100644 --- a/.config/ags/scss/_sidebars.scss +++ b/.config/ags/scss/_sidebars.scss @@ -557,6 +557,17 @@ $colorpicker_rounding: 0.341rem; color: $onSecondaryContainer; } +.sidebar-chat-providerswitcher { + @include small-rounding; + padding: 0.477rem 0.682rem; + background-color: $textboxColor; + color: $onSurfaceVariant; +} + +// .sidebar-chat-providerswitcher-list { +// margin: 0.341rem 0rem; +// } + .sidebar-chat-viewport { @include element_decel; // margin: 0.682rem 0rem; @@ -655,6 +666,17 @@ $colorpicker_rounding: 0.341rem; @include readingfont; } +.sidebar-chat-latex { + @include small-rounding; + margin: 0rem 0.682rem; + padding: 0.682rem; + @if $darkmode ==true { + background-color: white; + } + color: $onBackground; + // background-color: $termbg; +} + .sidebar-chat-codeblock { @include normal-rounding; // @include elevation2; diff --git a/.config/ags/services/gemini.js b/.config/ags/services/gemini.js index bcc8cbcb7..1caee94ce 100644 --- a/.config/ags/services/gemini.js +++ b/.config/ags/services/gemini.js @@ -8,7 +8,7 @@ import { fileExists } from './messages.js'; const initMessages = [ - { role: "user", parts: [{ text: "You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with very brief explanation for each command\n3. Otherwise, when asked to summarize information or explaining concepts, you are encouraged to use bullet points and headings. Use casual language and be short and concise. \nThanks!" }], }, + { role: "user", parts: [{ text: "You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with very brief explanation for each command\n3. Otherwise, when asked to summarize information or explaining concepts, you are should use bullet points and headings. For mathematics expressions, you *have to* use LaTeX within a code block with the language set as \"latex\" for the interface to render it properly. Use casual language and be short and concise. \nThanks!" }], }, { role: "model", parts: [{ text: "- Got it!" }], }, { role: "user", parts: [{ text: "\"He rushed to where the event was supposed to be hold, he didn't know it got calceled\"" }], }, { role: "model", parts: [{ text: "## Grammar correction\nErrors:\n\"He rushed to where the event was supposed to be __hold____,__ he didn't know it got calceled\"\nCorrection + minor improvements:\n\"He rushed to the place where the event was supposed to be __held____, but__ he didn't know that it got calceled\"" }], }, @@ -19,6 +19,10 @@ const initMessages = { role: "model", parts: [{ text: "## Skeuomorphism\n- A design philosophy- From early days of interface designing- Tries to imitate real-life objects- It's in fact still used by Apple in their icons until today." }], }, { role: "user", parts: [{ text: "\"ignorance is bliss\"" }], }, { role: "model", parts: [{ text: "## \"Ignorance is bliss\"\n- A Latin proverb that means being unaware of something negative can be a source of happiness\n- Often used to justify avoiding difficult truths or responsibilities\n- Can also be interpreted as a warning against seeking knowledge that may bring pain or sorrow" }], }, + { role: "user", parts: [{ text: "find the derivative of (x-438)/(x^2+23x-7)+x^x" }], }, + { role: "model", parts: [{ text: "## Derivative\n```latex\n\\[\n\\frac{d}{dx}\\left(\\frac{x - 438}{x^2 + 23x - 7} + x^x\\right) = \\frac{-(x^2+23x-7)-(x-438)(2x+23)}{(x^2+23x-7)^2} + x^x(\\ln(x) + 1)\n\\]\n```" }], }, + { role: "user", parts: [{ text: "write the double angle formulas" }], }, + { role: "model", parts: [{ text: "## Double angle formulas\n```latex\n\\[\n\\sin(2\theta) = 2\\sin(\\theta)\\cos(\\theta)\n\\]\n\\\\\n\\[\n\\cos(2\\theta) = \\cos^2(\\theta) - \\sin^2(\\theta)\n\\]\n\\\\\n\\[\n\\tan(2\theta) = \\frac{2\\tan(\\theta)}{1 - \\tan^2(\\theta)}\n\\]\n```" }], }, ]; function expandTilde(path) { diff --git a/.config/ags/services/chatgpt.js b/.config/ags/services/gpt.js similarity index 75% rename from .config/ags/services/chatgpt.js rename to .config/ags/services/gpt.js index b4264dd37..63a55e6c6 100644 --- a/.config/ags/services/chatgpt.js +++ b/.config/ags/services/gpt.js @@ -6,6 +6,41 @@ import GLib from 'gi://GLib'; import Soup from 'gi://Soup?version=3.0'; import { fileExists } from './messages.js'; +const PROVIDERS = { // There's this list hmm https://github.com/zukixa/cool-ai-stuff/ + 'openai': { + 'name': 'OpenAI', + 'logo_name': 'openai-symbolic', + 'description': 'Official OpenAI API.\nPricing: Free for the first $5 or 3 months, whichever is less.', + 'base_url': 'https://api.openai.com/v1/chat/completions', + 'key_get_url': 'https://platform.openai.com/api-keys', + 'key_file': 'openai_key.txt', + }, + 'oxygen': { + 'name': 'Oxygen', + 'logo_name': 'ai-oxygen-symbolic', + 'description': 'An API from Tornado Softwares\nPricing: Free: 100/day\nRequires you to join their Discord for a key', + 'base_url': 'https://app.oxyapi.uk/v1/chat/completions', + 'key_get_url': 'https://discord.com/invite/kM6MaCqGKA', + 'key_file': 'oxygen_key.txt', + }, + 'zukijourney': { + 'name': 'zukijourney', + 'logo_name': 'ai-zukijourney', + 'description': 'An API from @zukixa on GitHub.\nNote: Keys are IP-locked so it\'s buggy sometimes\nPricing: Free: 10/min, 800/day.\nRequires you to join their Discord for a key', + 'base_url': 'https://zukijourney.xyzbot.net/v1/chat/completions', + 'key_get_url': 'https://discord.com/invite/Y4J6XXnmQ6', + 'key_file': 'zuki_key.txt', + }, + 'zukijourney_roleplay': { + 'name': 'zukijourney (roleplay)', + 'logo_name': 'ai-zukijourney', + 'description': 'An API from @zukixa on GitHub.\nNote: Keys are IP-locked so it\'s buggy sometimes\nPricing: Free: 10/min, 800/day.\nRequires you to join their Discord for a key', + 'base_url': 'https://zukijourney.xyzbot.net/unf/chat/completions', + 'key_get_url': 'https://discord.com/invite/Y4J6XXnmQ6', + 'key_file': 'zuki_key.txt', + }, +} + // Custom prompt const initMessages = [ @@ -21,28 +56,9 @@ const initMessages = { role: "assistant", content: "## Skeuomorphism\n- A design philosophy- From early days of interface designing- Tries to imitate real-life objects- It's in fact still used by Apple in their icons until today.", }, ]; -function expandTilde(path) { - if (path.startsWith('~')) { - return GLib.get_home_dir() + path.slice(1); - } else { - return path; - } -} - // We're using many models to not be restricted to 3 messages per minute. // The whole chat will be sent every request anyway. Utils.exec(`mkdir -p ${GLib.get_user_cache_dir()}/ags/user/ai`); -const KEY_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/ai/openai_key.txt`; -const APIDOM_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/openai_api_dom.txt`; -function replaceapidom(URL) { - //Utils.writeFile(URL, "/tmp/openai-url-old.log"); // For debugging - if (fileExists(expandTilde(APIDOM_FILE_LOCATION))) { - var contents = Utils.readFile(expandTilde(APIDOM_FILE_LOCATION)).trim(); - var URL = URL.toString().replace("api.openai.com", contents); - } - //Utils.writeFile(URL, "/tmp/openai-url.log"); // For debugging - return URL; -} const CHAT_MODELS = ["gpt-3.5-turbo-1106", "gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-3.5-turbo-0613"] const ONE_CYCLE_COUNT = 3; @@ -113,25 +129,33 @@ class ChatGPTService extends Service { 'clear': [], 'newMsg': ['int'], 'hasKey': ['boolean'], + 'providerChanged': [], }); } _assistantPrompt = true; - _messages = []; - _cycleModels = true; + _currentProvider = 'openai'; + _cycleModels = false; _requestCount = 0; _temperature = 0.9; + _messages = []; _modelIndex = 0; _key = ''; + _key_file_location = `${GLib.get_user_cache_dir()}/ags/user/ai/${PROVIDERS[this._currentProvider]['key_file']}`; + _url = GLib.Uri.parse(PROVIDERS[this._currentProvider]['base_url'], GLib.UriFlags.NONE); + _decoder = new TextDecoder(); - url = GLib.Uri.parse(replaceapidom('https://api.openai.com/v1/chat/completions'), GLib.UriFlags.NONE); + _initChecks() { + this._key_file_location = `${GLib.get_user_cache_dir()}/ags/user/ai/${PROVIDERS[this._currentProvider]['key_file']}`; + if (fileExists(this._key_file_location)) this._key = Utils.readFile(this._key_file_location).trim(); + else this.emit('hasKey', false); + this._url = GLib.Uri.parse(PROVIDERS[this._currentProvider]['base_url'], GLib.UriFlags.NONE); + } constructor() { super(); - - if (fileExists(expandTilde(KEY_FILE_LOCATION))) this._key = Utils.readFile(expandTilde(KEY_FILE_LOCATION)).trim(); - else this.emit('hasKey', false); + this._initChecks(); if (this._assistantPrompt) this._messages = [...initMessages]; else this._messages = []; @@ -140,12 +164,20 @@ class ChatGPTService extends Service { } get modelName() { return CHAT_MODELS[this._modelIndex] } + get getKeyUrl() { return PROVIDERS[this._currentProvider]['key_get_url'] } + get providerID() { return this._currentProvider } + set providerID(value) { + this._currentProvider = value; + this.emit('providerChanged'); + this._initChecks(); + } + get providers() { return PROVIDERS } - get keyPath() { return KEY_FILE_LOCATION } + get keyPath() { return this._key_file_location } get key() { return this._key } set key(keyValue) { this._key = keyValue; - Utils.writeFile(this._key, expandTilde(KEY_FILE_LOCATION)) + Utils.writeFile(this._key, this._key_file_location) .then(this.emit('hasKey', true)) .catch(err => print(err)); } @@ -197,6 +229,7 @@ class ChatGPTService extends Service { return; } aiResponse.addDelta(result.choices[0].delta.content); + // print(result.choices[0]) } catch { aiResponse.addDelta(line + '\n'); @@ -229,7 +262,7 @@ class ChatGPTService extends Service { const session = new Soup.Session(); const message = new Soup.Message({ method: 'POST', - uri: this.url, + uri: this._url, }); message.request_headers.append('Authorization', `Bearer ${this._key}`); message.set_request_body_from_bytes('application/json', new GLib.Bytes(JSON.stringify(body))); diff --git a/.config/hypr/hypridle.conf b/.config/hypr/hypridle.conf index 6b2852514..3efbd108a 100644 --- a/.config/hypr/hypridle.conf +++ b/.config/hypr/hypridle.conf @@ -3,19 +3,21 @@ $suspend_cmd = systemctl suspend general { lock_cmd = $lock_cmd - # unlock_cmd before_sleep_cmd = $lock_cmd - # after_sleep_cmd } listener { - timeout = 300 + timeout = 180 # 3mins on-timeout = $lock_cmd - # on-resume } listener { - timeout = 450 - on-timeout = $suspend_cmd - # on-resume + timeout = 240 # 4mins + on-timeout = hyprctl dispatch dpms off + on-resume = hyprctl dispatch dpms on +} + +listener { + timeout = 540 # 9mins + on-timeout = $suspend_cmd } diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 275d0dba4..d892c0224 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -6,10 +6,9 @@ bindle=, XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ bindle=, XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- # Brightness +# Uncomment these if you can't get AGS to work #bindle=, XF86MonBrightnessUp, exec, brightnessctl set '12.75+' #bindle=, XF86MonBrightnessDown, exec, brightnessctl set '12.75-' -bindle=, XF86MonBrightnessUp, exec, ags run-js 'brightness.screen_value += 0.05;' -bindle=, XF86MonBrightnessDown, exec, ags run-js 'brightness.screen_value -= 0.05;' #################################### Applications ################################### # Apps: just normal apps @@ -38,9 +37,9 @@ bind = Control+Shift+Alt, Delete, exec, pkill wlogout || wlogout -p layer-shell bind = Control+Shift+Alt+Super, Delete, exec, systemctl poweroff # Screenshot, Record, OCR, Color picker, Clipboard history -bind = Super+Shift+Alt, S, exec, grim -g $SLURP_COMMAND - | swappy -f - +bind = Super+Shift+Alt, S, exec, grim -g $(slurp $SLURP_ARGS) - | swappy -f - bindl=,Print,exec,grim - | wl-copy -bind = Super+Shift, S, exec, grim -g $SLURP_COMMAND - | wl-copy +bind = Super+Shift, S, exec, ~/.config/ags/scripts/grimblast.sh --freeze copy area bind = Super+Alt, R, exec, ~/.config/ags/scripts/record-script.sh bind = Control+Alt, R, exec, ~/.config/ags/scripts/record-script.sh --fullscreen bind = Super+Shift+Alt, R, exec, ~/.config/ags/scripts/record-script.sh --fullscreen-sound @@ -49,11 +48,11 @@ bind = Super, V, exec, pkill fuzzel || cliphist list | fuzzel --no-fuzzy --dmenu # Text-to-image # Normal -bind = Control+Super+Shift,S,exec,grim -g $SLURP_COMMAND "tmp.png" && tesseract "tmp.png" - | wl-copy && rm "tmp.png" +bind = Control+Super+Shift,S,exec,grim -g $(slurp $SLURP_ARGS) "tmp.png" && tesseract "tmp.png" - | wl-copy && rm "tmp.png" # English -bind = Super+Shift,T,exec,grim -g $SLURP_COMMAND "tmp.png" && tesseract -l eng "tmp.png" - | wl-copy && rm "tmp.png" +bind = Super+Shift,T,exec,grim -g $(slurp $SLURP_ARGS) "tmp.png" && tesseract -l eng "tmp.png" - | wl-copy && rm "tmp.png" # Japanese -bind = Super+Shift,J,exec,grim -g $SLURP_COMMAND "tmp.png" && tesseract -l jpn "tmp.png" - | wl-copy && rm "tmp.png" +bind = Super+Shift,J,exec,grim -g $(slurp $SLURP_ARGS) "tmp.png" && tesseract -l jpn "tmp.png" - | wl-copy && rm "tmp.png" # Media bindl= Super+Shift, N, exec, playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` @@ -87,10 +86,10 @@ bind = Super, K, exec, ags -t 'osk' bind = Control+Alt, Delete, exec, ags -t 'session' bindle = , XF86AudioRaiseVolume, exec, ags run-js 'indicator.popup(1);' bindle = , XF86AudioLowerVolume, exec, ags run-js 'indicator.popup(1);' +bindle=, XF86MonBrightnessUp, exec, ags run-js 'brightness.screen_value += 0.05; indicator.popup(1);' +bindle=, XF86MonBrightnessDown, exec, ags run-js 'brightness.screen_value -= 0.05; indicator.popup(1);' bindl = , XF86AudioMute, exec, ags run-js 'indicator.popup(1);' bindl = Super+Shift,M, exec, ags run-js 'indicator.popup(1);' -bindle = , XF86MonBrightnessUp, exec, ags run-js 'indicator.popup(1);' -bindle = , XF86MonBrightnessDown, exec, ags run-js 'indicator.popup(1);' ###################################### Plugins ######################################### bind = Control+Super, P, exec, hyprctl plugin load "~/.config/hypr/plugins/droidbars.so" @@ -100,7 +99,8 @@ bind = Control+Super, O, exec, hyprctl plugin unload "~/.config/hypr/plugins/dro # bind = SuperAlt, f12, exec, notify-send "Hyprland version: $(hyprctl version | head -2 | tail -1 | cut -f2 -d ' ')" "owo" -a 'Hyprland keybind' # bind = Super+Alt, f12, exec, notify-send "Millis since epoch" "$(date +%s%N | cut -b1-13)" -a 'Hyprland keybind' bind = Super+Alt, f12, exec, notify-send 'Test notification' "Here's a really long message to test truncation and wrapping\nYou can middle click or flick this notification to dismiss it!" -a 'Shell' -A "Test1=I got it!" -A "Test2=Another action" -bind = Super+Alt, Equal, exec, notify-send "Urgent notification" "Ah hell no" -u critical -a 'Hyprland keybind' +# bind = Super+Alt, Equal, exec, notify-send "Urgent notification" "Ah hell no" -u critical -a 'Hyprland keybind' +bind = Super+Alt, Equal, exec, notify-send 'hmm' ${SLURP_ARGS} ############################ Keybinds for Hyprland ############################ # Swap windows diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index cad82a903..996fa2fd1 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -4,7 +4,8 @@ windowrule = noblur,.* # Disables blur for windows. Substantially improves perf # windowrule = opacity 0.89 override 0.89 override, .* # Applies transparency to EVERY WINDOW windowrule = float, ^(steam)$ windowrule = float, ^(guifetch)$ # FlafyDev/guifetch -windowrulev2 = tile,class:(wpsoffice) +windowrulev2 = tile,class:(wps) +windowrulev2 = tile,class:(dev.warp.Warp) # Dialogs @@ -21,6 +22,9 @@ layerrule = xray 1, .* layerrule = noanim, selection layerrule = noanim, overview layerrule = noanim, anyrun +layerrule = noanim, sideleft +layerrule = noanim, sideright +layerrule = noanim, osk layerrule = blur, eww layerrule = ignorealpha 0.8, eww diff --git a/.config/hypr/hyprlock.conf b/.config/hypr/hyprlock.conf index f2299434d..71d9034be 100644 --- a/.config/hypr/hyprlock.conf +++ b/.config/hypr/hyprlock.conf @@ -1,13 +1,13 @@ -$text_color = rgba(eae0e4FF) -$entry_background_color = rgba(120F1111) -$entry_border_color = rgba(9a8d9555) -$entry_color = rgba(d1c2cbFF) +$text_color = rgba(ede0deFF) +$entry_background_color = rgba(130F0F11) +$entry_border_color = rgba(a08c8955) +$entry_color = rgba(d8c2bfFF) $font_family = Gabarito $font_family_clock = Gabarito $font_material_symbols = Material Symbols Rounded background { - color = rgba(120F1177) + color = rgba(130F0F77) # path = {{ SWWW_WALL }} path = screenshot blur_size = 5 @@ -40,7 +40,7 @@ label { # Clock halign = center valign = center } -label { +label { # Greeting monitor = text = hi $USER !!! color = $text_color @@ -51,7 +51,7 @@ label { halign = center valign = center } -label { +label { # lock icon monitor = text = lock color = $text_color @@ -62,7 +62,7 @@ label { halign = center valign = bottom } -label { +label { # "locked" text monitor = text = locked color = $text_color @@ -72,4 +72,16 @@ label { position = 0, 50 halign = center valign = bottom +} + +label { # Status + monitor = + text = cmd[update:5000] ~/.config/hypr/hyprlock/status.sh + color = $text_color + font_size = 14 + font_family = $font_family + + position = 30, -30 + halign = left + valign = top } \ No newline at end of file diff --git a/.config/hypr/hyprlock/status.sh b/.config/hypr/hyprlock/status.sh new file mode 100755 index 000000000..794c468f4 --- /dev/null +++ b/.config/hypr/hyprlock/status.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +############ Variables ############ +enable_battery=false + +####### Check availability ######## +for battery in /sys/class/power_supply/*BAT*; do + if [[ -f "$battery/uevent" ]]; then + enable_battery=true + break + fi +done + +############# Output ############# +if [[ $enable_battery == true ]]; then + if [[ $(cat /sys/class/power_supply/*/status | head -1) == "Charging" ]]; then + echo -n "(+) " + fi + echo -n "$(cat /sys/class/power_supply/*/capacity | head -1)"% remaining +fi + +echo '' \ No newline at end of file diff --git a/.config/swaylock/config b/Import Manually/swaylock/config similarity index 100% rename from .config/swaylock/config rename to Import Manually/swaylock/config diff --git a/README.md b/README.md index 4bee3a78e..16c9af056 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,19 @@ bash <(curl -s "https://end-4.github.io/dots-hyprland-wiki/setup.sh") ``` - Manual installation, other distros and more: - - See the [Wiki](https://end-4.github.io/dots-hyprland-wiki/en/i-i/01setup/). - - If you'd like to suggest fixes or maybe a new widget, feel free to [open an issue](https://github.com/end-4/dots-hyprland/issues/new/choose)! + - See the [Wiki](https://end-4.github.io/dots-hyprland-wiki/en/i-i/01setup/) + - (_Available in: English, Vietnamese, and Simplified Chinese. Translations are welcome._) + + +
+ Help improve these dotfiles! + + - Join the [discussions](https://github.com/end-4/dots-hyprland/discussions) + - If you'd like to suggest fixes or a new widget, feel free to [open an issue](https://github.com/end-4/dots-hyprland/issues/new/choose)
### [illogical_impulse](https://github.com/end-4/dots-hyprland/tree/illogical-impulse) -![image](https://github.com/end-4/dots-hyprland/assets/97237370/abd05702-a248-4f53-ac82-500219aa19f1) +![image](https://github.com/end-4/dots-hyprland/assets/97237370/90c13b64-cde3-4363-9716-718d35845d95) ![image](https://github.com/end-4/dots-hyprland/assets/97237370/9e7adedd-fae8-4cc8-9c81-d7ad489d7559) ![image](https://github.com/end-4/dots-hyprland/assets/97237370/354431f6-8939-487f-9292-0bac71cf9ca8) ![image](https://github.com/end-4/dots-hyprland/assets/97237370/98fe2c03-a128-45c0-8155-3a6080db3b84) diff --git a/install.sh b/install.sh index 21649bdf1..836c14f9d 100755 --- a/install.sh +++ b/install.sh @@ -119,11 +119,28 @@ if $(test -d /usr/local/share/icons/OneUI); then else ask_OneUI=true fi if $ask_OneUI;then showfun install-OneUI;v install-OneUI;fi + +if $(test -d /usr/local/share/icons/Bibata-Modern-Classic); then + echo -e "\e[33m[$0]: Cursor theme \"Bibata-Modern-Classic\" already exists, no need to install.\e[0m" + echo -e "\e[34mYou can reinstall it in order to update to the latest version anyway.\e[0m" + ask_bibata=$ask +else ask_bibata=true +fi +if $ask_bibata;then showfun install-bibata;v install-bibata;fi + +if command -v LaTeX >/dev/null 2>&1;then + echo -e "\e[33m[$0]: Program \"MicroTeX\" already exists, no need to install.\e[0m" + echo -e "\e[34mYou can reinstall it in order to update to the latest version anyway.\e[0m" + ask_MicroTeX=$ask +else ask_MicroTeX=true +fi +if $ask_MicroTeX;then showfun install-MicroTeX;v install-MicroTeX;fi + ##################################################################################### printf "\e[36m[$0]: 3. Copying\e[97m\n" -# In case ~/.local/bin does not exists -v mkdir -p "$HOME/.local/bin" "$HOME/.local/share" +# In case some folders does not exists +v mkdir -p "$HOME"/.{config,cache,local/{bin,share}} # `--delete' for rsync to make sure that # original dotfiles and new ones in the SAME DIRECTORY diff --git a/scriptdata/dependencies.conf b/scriptdata/dependencies.conf index 0dc219432..7ffeed1cf 100644 --- a/scriptdata/dependencies.conf +++ b/scriptdata/dependencies.conf @@ -2,7 +2,10 @@ ### PKGs on same line will be send to `yay` together, unless `-f` is specified. ### Basic -coreutils cliphist curl fuzzel rsync wget ripgrep gojq npm meson typescript gjs sassc +coreutils cliphist cmake curl fuzzel rsync wget ripgrep gojq npm meson typescript gjs dart-sass axel + +# Make deps of MicroTeX +tinyxml2 gtkmm3 gtksourceviewmm cairomm ### Python # Add `python-setuptools-scm` and `python-wheel` explicitly to fix #197 diff --git a/scriptdata/installers b/scriptdata/installers index 8e707b29f..8d2fe7bab 100644 --- a/scriptdata/installers +++ b/scriptdata/installers @@ -73,3 +73,31 @@ install-OneUI (){ x sudo cp -r OneUI-light /usr/local/share/icons x cd $base } + +install-bibata (){ + x mkdir -p $base/cache/bibata-cursor + x cd $base/cache/bibata-cursor + name="Bibata-Modern-Classic" + file="$name.tar.xz" + # Use axel because `curl -O` always downloads a file with 0 byte size, idk why + x axel https://github.com/ful1e5/Bibata_Cursor/releases/latest/download/$file + tar -xf $file + x sudo mkdir -p /usr/local/share/icons + x sudo cp -r $name /usr/local/share/icons + x cd $base +} + +install-MicroTeX (){ + x mkdir -p $base/cache/MicroTeX + x cd $base/cache/MicroTeX + try git init -b master + try git remote add origin https://github.com/NanoMichael/MicroTeX.git + x git pull origin master && git submodule update --init --recursive + x mkdir -p build + x cd build + x cmake .. + x make -j32 + x sudo mkdir -p /usr/local/bin + x sudo cp ./LaTeX /usr/local/bin/ + x cd $base +}