diff --git a/.config/ags/assets/arch-symbolic.svg b/.config/ags/assets/arch-symbolic.svg new file mode 100644 index 000000000..7de9094e0 --- /dev/null +++ b/.config/ags/assets/arch-symbolic.svg @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.config/ags/assets/debian-symbolic.svg b/.config/ags/assets/debian-symbolic.svg new file mode 100644 index 000000000..252f85334 --- /dev/null +++ b/.config/ags/assets/debian-symbolic.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/.config/ags/assets/fedora-symbolic.svg b/.config/ags/assets/fedora-symbolic.svg new file mode 100644 index 000000000..1a4e8c873 --- /dev/null +++ b/.config/ags/assets/fedora-symbolic.svg @@ -0,0 +1,38 @@ + + + + + + + diff --git a/.config/ags/assets/flatpak-symbolic.svg b/.config/ags/assets/flatpak-symbolic.svg new file mode 100644 index 000000000..0c2bf6280 --- /dev/null +++ b/.config/ags/assets/flatpak-symbolic.svg @@ -0,0 +1,52 @@ + + + + + Flatpak + + + + + Flatpak + + + + diff --git a/.config/ags/assets/google-gemini-symbolic.svg b/.config/ags/assets/google-gemini-symbolic.svg index 9b00458f9..81f6729b9 100644 --- a/.config/ags/assets/google-gemini-symbolic.svg +++ b/.config/ags/assets/google-gemini-symbolic.svg @@ -3,17 +3,20 @@ + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> ionicons-v5_logos + style="stroke-width:0.587643" /> + style="fill:#000000;stroke-width:2.22805" /> + + + + ionicons-v5_logos + + + diff --git a/.config/ags/assets/nixos-symbolic.svg b/.config/ags/assets/nixos-symbolic.svg new file mode 100644 index 000000000..b697b0d1a --- /dev/null +++ b/.config/ags/assets/nixos-symbolic.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + diff --git a/.config/ags/assets/openai-symbolic.svg b/.config/ags/assets/openai-symbolic.svg index e04db75a5..8ffc912ae 100644 --- a/.config/ags/assets/openai-symbolic.svg +++ b/.config/ags/assets/openai-symbolic.svg @@ -1 +1,38 @@ - \ No newline at end of file + + + + + + diff --git a/.config/ags/assets/ubuntu-symbolic.svg b/.config/ags/assets/ubuntu-symbolic.svg new file mode 100644 index 000000000..07746c9f6 --- /dev/null +++ b/.config/ags/assets/ubuntu-symbolic.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.config/ags/config.js b/.config/ags/config.js index 4e9c0bb96..e52568f87 100644 --- a/.config/ags/config.js +++ b/.config/ags/config.js @@ -1,8 +1,4 @@ "use strict"; -const gi = imports.gi; -const availableModules = Object.keys(gi); -print("Available modules: " + availableModules.join(', ')); - // Import import Gdk from 'gi://Gdk'; import App from 'resource:///com/github/Aylur/ags/app.js' @@ -68,7 +64,7 @@ export default { }; // Stuff that don't need to be toggled. And they're async so ugh... -// Bar().catch(print); // Use this to debug the bar +// Bar().catch(print); // Use this to debug the bar. Single monitor only. forMonitors(Bar); forMonitors(BarCornerTopleft); -forMonitors(BarCornerTopright); \ No newline at end of file +forMonitors(BarCornerTopright); diff --git a/.config/ags/data/keyboardlayouts.js b/.config/ags/data/keyboardlayouts.js old mode 100644 new mode 100755 index 26cb23d63..cb92b8a3e --- a/.config/ags/data/keyboardlayouts.js +++ b/.config/ags/data/keyboardlayouts.js @@ -71,7 +71,9 @@ export const oskLayouts = { { keytype: "normal", label: "\\", labelShift: "|", shape: "expand", keycode: 43 } ], [ - { keytype: "normal", label: "Caps", shape: "caps", keycode: 58 }, + //{ keytype: "normal", label: "Caps", shape: "caps", keycode: 58 }, // not needed as double-pressing shift does that + { keytype: "spacer", label: "", shape: "empty" }, + { keytype: "spacer", label: "", shape: "empty" }, { keytype: "normal", label: "a", labelShift: "A", shape: "normal", keycode: 30 }, { keytype: "normal", label: "s", labelShift: "S", shape: "normal", keycode: 31 }, { keytype: "normal", label: "d", labelShift: "D", shape: "normal", keycode: 32 }, @@ -86,7 +88,7 @@ export const oskLayouts = { { keytype: "normal", label: "Enter", shape: "expand", keycode: 28 } ], [ - { keytype: "modkey", label: "Shift", shape: "shift", keycode: 42 }, + { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "shift", keycode: 42 }, { keytype: "normal", label: "z", labelShift: "Z", shape: "normal", keycode: 44 }, { keytype: "normal", label: "x", labelShift: "X", shape: "normal", keycode: 45 }, { keytype: "normal", label: "c", labelShift: "C", shape: "normal", keycode: 46 }, @@ -97,7 +99,7 @@ export const oskLayouts = { { keytype: "normal", label: ",", labelShift: "<", shape: "normal", keycode: 51 }, { keytype: "normal", label: ".", labelShift: ">", shape: "normal", keycode: 52 }, { keytype: "normal", label: "/", labelShift: "?", shape: "normal", keycode: 53 }, - { keytype: "modkey", label: "Shift", shape: "expand", keycode: 54 } + { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "expand", keycode: 54 } // optional ], [ { keytype: "modkey", label: "Ctrl", shape: "control", keycode: 29 }, @@ -110,5 +112,107 @@ export const oskLayouts = { { keytype: "modkey", label: "Ctrl", shape: "control", keycode: 97 } ] ] + }, + qwertz_full: { + name: "QWERTZ - Full", + name_short: "DE", + comment: "Keyboard layout commonly used in German-speaking countries", + keys: [ + [ + { keytype: "normal", label: "Esc", shape: "fn", keycode: 1 }, + { keytype: "normal", label: "F1", shape: "fn", keycode: 59 }, + { keytype: "normal", label: "F2", shape: "fn", keycode: 60 }, + { keytype: "normal", label: "F3", shape: "fn", keycode: 61 }, + { keytype: "normal", label: "F4", shape: "fn", keycode: 62 }, + { keytype: "normal", label: "F5", shape: "fn", keycode: 63 }, + { keytype: "normal", label: "F6", shape: "fn", keycode: 64 }, + { keytype: "normal", label: "F7", shape: "fn", keycode: 65 }, + { keytype: "normal", label: "F8", shape: "fn", keycode: 66 }, + { keytype: "normal", label: "F9", shape: "fn", keycode: 67 }, + { keytype: "normal", label: "F10", shape: "fn", keycode: 68 }, + { keytype: "normal", label: "F11", shape: "fn", keycode: 87 }, + { keytype: "normal", label: "F12", shape: "fn", keycode: 88 }, + { keytype: "normal", label: "Druck", shape: "fn", keycode: 99 }, + { keytype: "normal", label: "Entf", shape: "fn", keycode: 111 } + ], + [ + { keytype: "normal", label: "^", labelShift: "°", labelAlt: "′", shape: "normal", keycode: 41 }, + { keytype: "normal", label: "1", labelShift: "!", labelAlt: "¹", shape: "normal", keycode: 2 }, + { keytype: "normal", label: "2", labelShift: "\"", labelAlt: "²", shape: "normal", keycode: 3 }, + { keytype: "normal", label: "3", labelShift: "§", labelAlt: "³", shape: "normal", keycode: 4 }, + { keytype: "normal", label: "4", labelShift: "$", labelAlt: "¼", shape: "normal", keycode: 5 }, + { keytype: "normal", label: "5", labelShift: "%", labelAlt: "½", shape: "normal", keycode: 6 }, + { keytype: "normal", label: "6", labelShift: "&", labelAlt: "¬", shape: "normal", keycode: 7 }, + { keytype: "normal", label: "7", labelShift: "/", labelAlt: "{", shape: "normal", keycode: 8 }, + { keytype: "normal", label: "8", labelShift: "(", labelAlt: "[", shape: "normal", keycode: 9 }, + { keytype: "normal", label: "9", labelShift: ")", labelAlt: "]", shape: "normal", keycode: 10 }, + { keytype: "normal", label: "0", labelShift: "=", labelAlt: "}", shape: "normal", keycode: 11 }, + { keytype: "normal", label: "ß", labelShift: "?", labelAlt: "\\", shape: "normal", keycode: 12 }, + { keytype: "normal", label: "´", labelShift: "`", labelAlt: "¸", shape: "normal", keycode: 13 }, + { keytype: "normal", label: "⟵", shape: "expand", keycode: 14 } + ], + [ + { keytype: "normal", label: "Tab ⇆", shape: "tab", keycode: 15 }, + { keytype: "normal", label: "q", labelShift: "Q", labelAlt: "@", shape: "normal", keycode: 16 }, + { keytype: "normal", label: "w", labelShift: "W", labelAlt: "ſ", shape: "normal", keycode: 17 }, + { keytype: "normal", label: "e", labelShift: "E", labelAlt: "€", shape: "normal", keycode: 18 }, + { keytype: "normal", label: "r", labelShift: "R", labelAlt: "¶", shape: "normal", keycode: 19 }, + { keytype: "normal", label: "t", labelShift: "T", labelAlt: "ŧ", shape: "normal", keycode: 20 }, + { keytype: "normal", label: "z", labelShift: "Z", labelAlt: "←", shape: "normal", keycode: 21 }, + { keytype: "normal", label: "u", labelShift: "U", labelAlt: "↓", shape: "normal", keycode: 22 }, + { keytype: "normal", label: "i", labelShift: "I", labelAlt: "→", shape: "normal", keycode: 23 }, + { keytype: "normal", label: "o", labelShift: "O", labelAlt: "ø", shape: "normal", keycode: 24 }, + { keytype: "normal", label: "p", labelShift: "P", labelAlt: "þ", shape: "normal", keycode: 25 }, + { keytype: "normal", label: "ü", labelShift: "Ü", labelAlt: "¨", shape: "normal", keycode: 26 }, + { keytype: "normal", label: "+", labelShift: "*", labelAlt: "~", shape: "normal", keycode: 27 }, + { keytype: "normal", label: "↵", shape: "expand", keycode: 28 } + ], + [ + //{ keytype: "normal", label: "Umschalt ⇩", shape: "caps", keycode: 58 }, + { keytype: "spacer", label: "", shape: "empty" }, + { keytype: "spacer", label: "", shape: "empty" }, + { keytype: "normal", label: "a", labelShift: "A", labelAlt: "æ", shape: "normal", keycode: 30 }, + { keytype: "normal", label: "s", labelShift: "S", labelAlt: "ſ", shape: "normal", keycode: 31 }, + { keytype: "normal", label: "d", labelShift: "D", labelAlt: "ð", shape: "normal", keycode: 32 }, + { keytype: "normal", label: "f", labelShift: "F", labelAlt: "đ", shape: "normal", keycode: 33 }, + { keytype: "normal", label: "g", labelShift: "G", labelAlt: "ŋ", shape: "normal", keycode: 34 }, + { keytype: "normal", label: "h", labelShift: "H", labelAlt: "ħ", shape: "normal", keycode: 35 }, + { keytype: "normal", label: "j", labelShift: "J", labelAlt: "", shape: "normal", keycode: 36 }, + { keytype: "normal", label: "k", labelShift: "K", labelAlt: "ĸ", shape: "normal", keycode: 37 }, + { keytype: "normal", label: "l", labelShift: "L", labelAlt: "ł", shape: "normal", keycode: 38 }, + { keytype: "normal", label: "ö", labelShift: "Ö", labelAlt: "˝", shape: "normal", keycode: 39 }, + { keytype: "normal", label: "ä", labelShift: 'Ä', labelAlt: "^", shape: "normal", keycode: 40 }, + { keytype: "normal", label: "#", labelShift: '\'', labelAlt: "’", shape: "normal", keycode: 43 }, + { keytype: "spacer", label: "", shape: "empty" }, + //{ keytype: "normal", label: "↵", shape: "expand", keycode: 28 } + ], + [ + { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "shift", keycode: 42 }, + { keytype: "normal", label: "<", labelShift: ">", labelAlt: "|", shape: "normal", keycode: 86 }, + { keytype: "normal", label: "y", labelShift: "Y", labelAlt: "»", shape: "normal", keycode: 44 }, + { keytype: "normal", label: "x", labelShift: "X", labelAlt: "«", shape: "normal", keycode: 45 }, + { keytype: "normal", label: "c", labelShift: "C", labelAlt: "¢", shape: "normal", keycode: 46 }, + { keytype: "normal", label: "v", labelShift: "V", labelAlt: "„", shape: "normal", keycode: 47 }, + { keytype: "normal", label: "b", labelShift: "B", labelAlt: "“", shape: "normal", keycode: 48 }, + { keytype: "normal", label: "n", labelShift: "N", labelAlt: "”", shape: "normal", keycode: 49 }, + { keytype: "normal", label: "m", labelShift: "M", labelAlt: "µ", shape: "normal", keycode: 50 }, + { keytype: "normal", label: ",", labelShift: ";", labelAlt: "·", shape: "normal", keycode: 51 }, + { keytype: "normal", label: ".", labelShift: ":", labelAlt: "…", shape: "normal", keycode: 52 }, + { keytype: "normal", label: "-", labelShift: "_", labelAlt: "–", shape: "normal", keycode: 53 }, + { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "expand", keycode: 54 }, // optional + ], + [ + { keytype: "modkey", label: "Strg", shape: "control", keycode: 29 }, + //{ keytype: "normal", label: "", shape: "normal", keycode: 125 }, // dangerous + { keytype: "modkey", label: "Alt", shape: "normal", keycode: 56 }, + { keytype: "normal", label: "Leertaste", shape: "space", keycode: 57 }, + { keytype: "modkey", label: "Alt Gr", shape: "normal", keycode: 100 }, + // { label: "Super", shape: "normal", keycode: 126 }, // dangerous + //{ keytype: "normal", label: "Menu", shape: "normal", keycode: 139 }, // doesn't work? + { keytype: "modkey", label: "Strg", shape: "control", keycode: 97 }, + { keytype: "normal", label: "⇦", shape: "normal", keycode: 105 }, + { keytype: "normal", label: "⇨", shape: "normal", keycode: 106 }, + ] + ] } -} +} \ No newline at end of file diff --git a/.config/ags/lib/md2pango.js b/.config/ags/lib/md2pango.js index ec94dd1f0..054752b14 100644 --- a/.config/ags/lib/md2pango.js +++ b/.config/ags/lib/md2pango.js @@ -1,208 +1,62 @@ -// SPDX-FileCopyrightText: 2021 Uwe Jugel -// SPDX-License-Identifier: MIT -// This file is part of md2pango (https://github.com/ubunatic/md2pango). +// Converts from Markdown to Pango. This does not support code blocks. +// For illogical-impulse, code blocks are treated separately, in their own GtkSourceView widgets. +// Partly inherited from https://github.com/ubunatic/md2pango -const monospaceFonts = 'JetBrains Mono NF, JetBrains Mono Nerd Font, JetBrains Mono NL, SpaceMono NF, SpaceMono Nerd Font, monospace' +const monospaceFonts = 'JetBrains Mono NF, JetBrains Mono Nerd Font, JetBrains Mono NL, SpaceMono NF, SpaceMono Nerd Font, monospace'; -const H1 = "H1", H2 = "H2", H3 = "H3", H4 = "H4", H5 = "H5", BULLET = "BULLET", NUMBERING = "NUMBERING", CODE = "CODE" -const BOLD = "BOLD", EMPH = "EMPH", INLCODE = "INLCODE", LINK = "LINK", HEXCOLOR = "HEXCOLOR", UND = "UND" - -let sub_h1, sub_h2, sub_h3, sub_h4, sub_h5 - -// m2p_sections defines how to detect special markdown sections. -// These expressions scan the full line to detect headings, lists, and code. -const m2p_sections = [ - sub_h1 = { name: H1, re: /^(#\s+)(.*)(\s*)$/, sub: "$2" }, - sub_h2 = { name: H2, re: /^(##\s+)(.*)(\s*)$/, sub: "$2" }, - sub_h3 = { name: H3, re: /^(###\s+)(.*)(\s*)$/, sub: "$2" }, - sub_h4 = { name: H4, re: /^(####\s+)(.*)(\s*)$/, sub: "$2" }, - sub_h5 = { name: H5, re: /^(#####\s+)(.*)(\s*)$/, sub: "$2" }, - { name: BULLET, re: /^(\s*)([\*\-]\s)(.*)(\s*)$/, sub: "$1• $3" }, - { name: NUMBERING, re: /^(\s*[0-9]+\.\s)(.*)(\s*)$/, sub: " $1$2" }, -] - -// m2p_styles defines how to replace inline styled text -const m2p_styles = [ - { name: BOLD, re: /(\*\*)(\S[\s\S]*?\S)(\*\*)/g, sub: "$2" }, - { name: UND, re: /(__)(\S[\s\S]*?\S)(__)/g, sub: "$2" }, - { name: EMPH, re: /\*(\S.*?\S)\*/g, sub: "$1" }, - // { name: EMPH, re: /_(\S.*?\S)_/g, sub: "$1" }, - { name: HEXCOLOR, re: /#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/g, sub: ` #$1 ` }, - { name: INLCODE, re: /(`)([^`]*)(`)/g, sub: ` $2 ` }, - // { name: UND, re: /(__|\*\*)(\S[\s\S]*?\S)(__|\*\*)/g, sub: "$2" }, -] - -const re_comment = /^\s*\s*$/ -const re_color = /^(\s*\s*)$/ -const re_reset = /()/ -const re_uri = /http[s]?:\/\/[^\s']*/ -const re_href = "/href='(http[s]?:\\/\\/[^\\s]*)'" -const re_atag = ".*(http[s]?:\\/\\/[^\\s]*).*/" -const re_h1line = /^===+\s*$/ -const re_h2line = /^---+\s*$/ - -const m2p_escapes = [ - [//, ''], - [/&/g, '&'], - [//g, '>'], -] - -const code_color_span = "" - -const escape_line = (line) => m2p_escapes.reduce((l, esc) => l.replace(...esc), line) - -const pad = (lines, start = 1, end = 1) => { - let len = lines.reduce((n, l) => l.length > n ? l.length : n, 0) - return lines.map((l) => l.padEnd(len + end, ' ').padStart(len + end + start, ' ')) +const replacements = { + 'indents': [ + { name: 'BULLET', re: /^(\s*)([\*\-]\s)(.*)(\s*)$/, sub: ' $1- $3' }, + { name: 'NUMBERING', re: /^(\s*[0-9]+\.\s)(.*)(\s*)$/, sub: ' $1 $2' }, + ], + 'escapes': [ + { name: 'COMMENT', re: //, sub: '' }, + { name: 'AMPERSTAND', re: /&/g, sub: '&' }, + { name: 'LESSTHAN', re: //g, sub: '>' }, + ], + 'sections': [ + { name: 'H1', re: /^(#\s+)(.*)(\s*)$/, sub: '$2' }, + { name: 'H2', re: /^(##\s+)(.*)(\s*)$/, sub: '$2' }, + { name: 'H3', re: /^(###\s+)(.*)(\s*)$/, sub: '$2' }, + { name: 'H4', re: /^(####\s+)(.*)(\s*)$/, sub: '$2' }, + { name: 'H5', re: /^(#####\s+)(.*)(\s*)$/, sub: '$2' }, + ], + 'styles': [ + { name: 'BOLD', re: /(\*\*)(\S[\s\S]*?\S)(\*\*)/g, sub: "$2" }, + { name: 'UND', re: /(__)(\S[\s\S]*?\S)(__)/g, sub: "$2" }, + { name: 'EMPH', re: /\*(\S.*?\S)\*/g, sub: "$1" }, + // { name: 'EMPH', re: /_(\S.*?\S)_/g, sub: "$1" }, + { name: 'HEXCOLOR', re: /#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/g, sub: '#$1' }, + { name: 'INLCODE', re: /(`)([^`]*)(`)/g, sub: '$2' }, + // { name: 'UND', re: /(__|\*\*)(\S[\s\S]*?\S)(__|\*\*)/g, sub: "$2" }, + ], } +const replaceCategory = (text, replaces) => { + for (const type of replaces) { + text = text.replace(type.re, type.sub); + } + return text; +} + +// Main function + export default (text) => { let lines = text.split('\n') - - // Indicates if the current line is within a code block - let is_code = false - let code_lines = [] - - let output = [] - let color_span_open = false - let tt_must_close = false - - const try_close_span = () => { - if (color_span_open) { - output.push('') - color_span_open = false - } - } - - const try_open_span = () => { - if (!color_span_open) { - output.push('') - color_span_open = false - } - } - + let output = []; + // Replace for (const line of lines) { - // first parse color macros in non-code texts - if (!is_code) { - let colors = line.match(re_color) - if (colors || line.match(re_reset)) { - try_close_span() - } - - if (colors) { - try_close_span() - if (color_span_open) { - close_span() - } - - let fg = colors[2] == 'fg' ? colors[3] : colors[5] == 'fg' ? colors[6] : '' - let bg = colors[2] == 'bg' ? colors[3] : colors[5] == 'bg' ? colors[6] : '' - let attrs = '' - - if (fg != '') { - attrs += ` foreground='${fg}'` - } - - if (bg != '') { - attrs += ` background='${bg}'` - } - - if (attrs != '') { - output.push(``) - color_span_open = true - } - } - } - - // all macros processed, let's remove remaining comments - if (line.match(re_comment)) { - continue - } - - // is this line an opening statement of a code block - let code_start = false - - // escape all non-verbatim text - let result = is_code ? line : escape_line(line) - - for (const { name, re, sub } of m2p_sections) { - if (line.match(re)) { - if (name === CODE) { - if (!is_code) { - // haven't been inside a code block, so ``` indicates - // that it is starting now - code_start = true - is_code = true - - if (color_span_open) { - // cannot color - result = '' - tt_must_close = false - } else { - result = code_color_span + '' - tt_must_close = true - } - } else { - // the code block ends now - is_code = false - output.push(...pad(code_lines).map(escape_line)) - code_lines = [] - result = '' - if (tt_must_close) { - result += '' - tt_must_close = false - } - } - } else { - if (is_code) { - result = line - } else { - result = line.replace(re, sub) - } - } - } - } - - if (is_code && !code_start) { - code_lines.push(result) - continue - } - - if (line.match(re_h1line)) { - output.push(`# ${output.pop()}`.replace(sub_h1.re, sub_h1.sub)) - continue - } - - if (line.match(re_h2line)) { - output.push(`## ${output.pop()}`.replace(sub_h2.re, sub_h2.sub)) - continue - } - - // all other text can be styled - for (const style of m2p_styles) { - result = result.replace(style.re, style.sub) - } - - // all raw urls can be linked if possible - let uri = result.match(re_uri) // look for any URI - let href = result.match(re_href) // and for URIs in href='' - let atag = result.match(re_atag) // and for URIs in - href = href && href[1] == uri - atag = href && atag[1] == uri - if (uri && (href || atag)) { - result = result.replace(uri, `${uri}`) - } - + let result = line; + result = replaceCategory(result, replacements.indents); + result = replaceCategory(result, replacements.escapes); + result = replaceCategory(result, replacements.sections); + result = replaceCategory(result, replacements.styles); output.push(result) } - - try_close_span() - - // remove trailing whitespaces + // Remove trailing whitespaces output = output.map(line => line.replace(/ +$/, '')) - - return output.join('\n') + return output.join('\n'); } export const markdownTest = `# Heading 1 @@ -227,5 +81,6 @@ export const markdownTest = `# Heading 1 myArray = [23, 123, 43, 54, '6969']; console.log('uwu'); \`\`\` -To update arch lincox, run \`sudo pacman -Syu\` +- Random instruction thing + - To update arch lincox, run \`sudo pacman -Syu\` `; \ No newline at end of file diff --git a/.config/ags/lib/notification.js b/.config/ags/lib/notification.js index 628b01177..47f2c2061 100644 --- a/.config/ags/lib/notification.js +++ b/.config/ags/lib/notification.js @@ -56,12 +56,6 @@ const NotificationIcon = (notifObject) => { Icon({ vpack: 'center', icon: icon, - setup: (self) => Utils.timeout(1, () => { - const styleContext = self.get_parent().get_style_context(); - const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL); - const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); - self.size = Math.max(width * 0.7, height * 0.7, 1); // im too lazy to add another box lol - }, self), }) : MaterialIcon(`${notifObject.urgency == 'critical' ? 'release_alert' : guessMessageType(notifObject.summary.toLowerCase())}`, 'hugerass', { diff --git a/.config/ags/scripts/quickscripts/nixos-trim-generations.sh b/.config/ags/scripts/quickscripts/nixos-trim-generations.sh new file mode 100755 index 000000000..98b59be95 --- /dev/null +++ b/.config/ags/scripts/quickscripts/nixos-trim-generations.sh @@ -0,0 +1,243 @@ +#!/usr/bin/env bash +set -euo pipefail + +## Defaults +keepGensDef=30; keepDaysDef=30 +keepGens=$keepGensDef; keepDays=$keepDaysDef + +## Usage +usage () { + printf "Usage:\n\t ./trim-generations.sh \n\n +(defaults are: Keep-Gens=$keepGensDef Keep-Days=$keepDaysDef Profile=user)\n\n" + printf "If you enter any parameters, you must enter all three, or none to use defaults.\n" + printf "Example:\n\t trim-generations.sh 15 10 home-manager\n" + printf " this will work on the home-manager profile and keep all generations from the\n" + printf "last 10 days, and keep at least 15 generations no matter how old.\n" + printf "\nProfiles available are:\tuser, home-manager, channels, system (root)\n" + printf "\n-h or --help prints this help text." +} + +if [ $# -eq 1 ]; then # if help requested + if [ $1 = "-h" ]; then + usage + exit 1; + fi + if [ $1 = "--help" ]; then + usage + exit 2; + fi + printf "Dont recognise your option exiting..\n\n" + usage + exit 3; + + elif [ $# -eq 0 ]; then # print the defaults + printf "The current defaults are:\n Keep-Gens=$keepGensDef Keep-Days=$keepDaysDef \n\n" + read -p "Keep these defaults? (y/n):" answer + + case "$answer" in + [yY1] ) + printf "Using defaults..\n" + ;; + [nN0] ) printf "ok, doing nothing, exiting..\n" + exit 6; + ;; + * ) printf "%b" "Doing nothing, exiting.." + exit 7; + ;; + esac +fi + +## Handle parameters (and change if root) +if [[ $EUID -ne 0 ]]; then # if not root + profile=$(readlink /home/$USER/.nix-profile) +else + if [ -d /nix/var/nix/profiles/system ]; then # maybe this or the other + profile="/nix/var/nix/profiles/system" + elif [ -d /nix/var/nix/profiles/default ]; then + profile="/nix/var/nix/profiles/default" + else + echo "Cant find profile for root. Exiting" + exit 8 + fi +fi +if (( $# < 1 )); then + printf "Keeping default: $keepGensDef generations OR $keepDaysDef days, whichever is more\n" +elif [[ $# -le 2 ]]; then + printf "\nError: Not enough arguments.\n\n" >&2 + usage + exit 1 +elif (( $# > 4)); then + printf "\nError: Too many arguments.\n\n" >&2 + usage + exit 2 +else + if [ $1 -lt 1 ]; then + printf "using Gen numbers less than 1 not recommended. Setting to min=1\n" + read -p "is that ok? (y/n): " asnwer + #printf "$asnwer" + case "$asnwer" in + [yY1] ) + printf "ok, continuing..\n" + ;; + [nN0] ) + printf "ok, doing nothing, exiting..\n" + exit 6; + ;; + * ) + printf "%b" "Doing nothing, exiting.." + exit 7; + ;; + esac + fi + if [ $2 -lt 0 ]; then + printf "using negative days number not recommended. Setting to min=0\n" + read -p "is that ok? (y/n): " asnwer + + case "$asnwer" in + [yY1] ) + printf "ok, continuing..\n" + ;; + [nN0] ) + printf "ok, doing nothing, exiting..\n" + exit 6; + ;; + * ) + printf "%b" "Doing nothing, exiting.." + exit 7; + ;; + esac + fi + keepGens=$1; keepDays=$2; + (( keepGens < 1 )) && keepGens=1 + (( keepDays < 0 )) && keepDays=0 + if [[ $EUID -ne 0 ]]; then + if [[ $3 == "user" ]] || [[ $3 == "default" ]]; then + profile=$(readlink /home/$USER/.nix-profile) + elif [[ $3 == "home-manager" ]]; then + # home-manager defaults to $XDG_STATE_HOME; otherwise, use + # `home-manager generations` and `nix-store --query --roots + # /nix/store/...` to figure out what reference is keeping the old + # generations alive. + profile="${XDG_STATE_HOME:-$HOME/.local/state}/nix/profiles/home-manager" + elif [[ $3 == "channels" ]]; then + profile="/nix/var/nix/profiles/per-user/$USER/channels" + else + printf "\nError: Do not understand your third argument. Should be one of: (user / home-manager/ channels)\n\n" + usage + exit 3 + fi + else + if [[ $3 == "system" ]]; then + profile="/nix/var/nix/profiles/system" + elif [[ $3 == "user" ]] || [[ $3 == "default" ]]; then + profile="/nix/var/nix/profiles/default" + else + printf "\nError: Do not understand your third argument. Should be one of: (user / system)\n\n" + usage + exit 3 + fi + fi + printf "OK! \t Keep Gens = $keepGens \t Keep Days = $keepDays\n\n" +fi + +printf "Operating on profile: \t $profile\n\n" + +## Runs at the end, to decide whether to delete profiles that match chosen parameters. +choose () { + local default="$1" + local prompt="$2" + local answer + + read -p "$prompt" answer + [ -z "$answer" ] && answer="$default" + + case "$answer" in + [yY1] ) #printf "answered yes!\n" + nix-env --delete-generations -p $profile ${!gens[@]} + exit 0 + ;; + [nN0] ) printf "Ok doing nothing exiting..\n" + exit 6; + ;; + * ) printf "%b" "Unexpected answer '$answer'!" >&2 + exit 7; + ;; + esac +} # end of function choose + +# printf "profile = $profile\n\n" +## Query nix-env for generations list +IFS=$'\n' nixGens=( $(nix-env --list-generations -p $profile | sed 's:^\s*::; s:\s*$::' | tr '\t' ' ' | tr -s ' ') ) +timeNow=$(date +%s) + +## Get info on oldest generation +IFS=' ' read -r -a oldestGenArr <<< "${nixGens[0]}" +oldestGen=${oldestGenArr[0]} +oldestDate=${oldestGenArr[1]} +printf "%-30s %s\n" "oldest generation:" $oldestGen +#oldestDate=${nixGens[0]:3:19} +printf "%-30s %s\n" "oldest generation created:" $oldestDate +oldestTime=$(date -d "$oldestDate" +%s) +oldestElapsedSecs=$((timeNow-oldestTime)) +oldestElapsedMins=$((oldestElapsedSecs/60)) +oldestElapsedHours=$((oldestElapsedMins/60)) +oldestElapsedDays=$((oldestElapsedHours/24)) +printf "%-30s %s\n" "minutes before now:" $oldestElapsedMins +printf "%-30s %s\n" "hours before now:" $oldestElapsedHours +printf "%-30s %s\n\n" "days before now:" $oldestElapsedDays + +## Get info on current generation +for i in "${nixGens[@]}"; do + IFS=' ' read -r -a iGenArr <<< "$i" + genNumber=${iGenArr[0]} + genDate=${iGenArr[1]} + if [[ "$i" =~ current ]]; then + currentGen=$genNumber + printf "%-30s %s\n" "current generation:" $currentGen + currentDate=$genDate + printf "%-30s %s\n" "current generation created:" $currentDate + currentTime=$(date -d "$currentDate" +%s) + currentElapsedSecs=$((timeNow-currentTime)) + currentElapsedMins=$((currentElapsedSecs/60)) + currentElapsedHours=$((currentElapsedMins/60)) + currentElapsedDays=$((currentElapsedHours/24)) + printf "%-30s %s\n" "minutes before now:" $currentElapsedMins + printf "%-30s %s\n" "hours before now:" $currentElapsedHours + printf "%-30s %s\n\n" "days before now:" $currentElapsedDays + fi +done + +## Compare oldest and current generations +timeBetweenOldestAndCurrent=$((currentTime-oldestTime)) +elapsedDays=$((timeBetweenOldestAndCurrent/60/60/24)) +generationsDiff=$((currentGen-oldestGen)) + +## Figure out what we should do, based on generations and options +if [[ elapsedDays -le keepDays ]]; then + printf "All generations are no more than $keepDays days older than current generation. \nOldest gen days difference from current gen: $elapsedDays \n\n\tNothing to do!\n" + exit 4; +elif [[ generationsDiff -lt keepGens ]]; then + printf "Oldest generation ($oldestGen) is only $generationsDiff generations behind current ($currentGen). \n\n\t Nothing to do!\n" + exit 5; +else + printf "\tSomething to do...\n" + declare -a gens + for i in "${nixGens[@]}"; do + IFS=' ' read -r -a iGenArr <<< "$i" + genNumber=${iGenArr[0]} + genDiff=$((currentGen-genNumber)) + genDate=${iGenArr[1]} + genTime=$(date -d "$genDate" +%s) + elapsedSecs=$((timeNow-genTime)) + genDaysOld=$((elapsedSecs/60/60/24)) + if [[ genDaysOld -gt keepDays ]] && [[ genDiff -ge keepGens ]]; then + gens["$genNumber"]="$genDate, $genDaysOld day(s) old" + fi + done + printf "\nFound the following generation(s) to delete:\n" + for K in "${!gens[@]}"; do + printf "generation $K \t ${gens[$K]}\n" + done + printf "\n" + choose "y" "Do you want to delete these? [Y/n]: " +fi diff --git a/.config/ags/scripts/wayland-idle-inhibitor.py b/.config/ags/scripts/wayland-idle-inhibitor.py new file mode 100755 index 000000000..5a6d0e442 --- /dev/null +++ b/.config/ags/scripts/wayland-idle-inhibitor.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +import sys +from dataclasses import dataclass +from signal import SIGINT, SIGTERM, signal +from threading import Event + +from pywayland.client.display import Display +from pywayland.protocol.idle_inhibit_unstable_v1.zwp_idle_inhibit_manager_v1 import ( + ZwpIdleInhibitManagerV1, +) +from pywayland.protocol.wayland.wl_compositor import WlCompositor +from pywayland.protocol.wayland.wl_registry import WlRegistryProxy +from pywayland.protocol.wayland.wl_surface import WlSurface + + +@dataclass +class GlobalRegistry: + surface: WlSurface | None = None + inhibit_manager: ZwpIdleInhibitManagerV1 | None = None + + +def handle_registry_global( + wl_registry: WlRegistryProxy, id_num: int, iface_name: str, version: int +) -> None: + global_registry: GlobalRegistry = wl_registry.user_data or GlobalRegistry() + + if iface_name == "wl_compositor": + compositor = wl_registry.bind(id_num, WlCompositor, version) + global_registry.surface = compositor.create_surface() # type: ignore + elif iface_name == "zwp_idle_inhibit_manager_v1": + global_registry.inhibit_manager = wl_registry.bind( + id_num, ZwpIdleInhibitManagerV1, version + ) + + +def main() -> None: + done = Event() + signal(SIGINT, lambda _, __: done.set()) + signal(SIGTERM, lambda _, __: done.set()) + + global_registry = GlobalRegistry() + + display = Display() + display.connect() + + registry = display.get_registry() # type: ignore + registry.user_data = global_registry + registry.dispatcher["global"] = handle_registry_global + + def shutdown() -> None: + display.dispatch() + display.roundtrip() + display.disconnect() + + display.dispatch() + display.roundtrip() + + if global_registry.surface is None or global_registry.inhibit_manager is None: + print("Wayland seems not to support idle_inhibit_unstable_v1 protocol.") + shutdown() + sys.exit(1) + + inhibitor = global_registry.inhibit_manager.create_inhibitor( # type: ignore + global_registry.surface + ) + + display.dispatch() + display.roundtrip() + + print("Inhibiting idle...") + done.wait() + print("Shutting down...") + + inhibitor.destroy() + + shutdown() + + +if __name__ == "__main__": + main() diff --git a/.config/ags/scss/_bar.scss b/.config/ags/scss/_bar.scss index 7ad45ed44..f83566092 100644 --- a/.config/ags/scss/_bar.scss +++ b/.config/ags/scss/_bar.scss @@ -285,6 +285,7 @@ $bar_subgroup_bg: $surfaceVariant; @include element_decel; min-height: 1.032rem; min-width: 1.032rem; + font-size: 1.032rem; } .bar-statusicons { diff --git a/.config/ags/scss/_common.scss b/.config/ags/scss/_common.scss index 0af4e65e6..f7530f59b 100644 --- a/.config/ags/scss/_common.scss +++ b/.config/ags/scss/_common.scss @@ -40,14 +40,14 @@ menu { animation-iteration-count: 1; } -menubar > menuitem { +menubar>menuitem { border-radius: 0.545rem; -gtk-outline-radius: 0.545rem; min-width: 13.636rem; min-height: 2.727rem; } -menu > menuitem { +menu>menuitem { padding: 0.4em 1.5rem; background: transparent; transition: 0.2s ease background; @@ -55,11 +55,12 @@ menu > menuitem { -gtk-outline-radius: 0.545rem; } -menu > menuitem:hover, -menu > menuitem:focus { +menu>menuitem:hover, +menu>menuitem:focus { background-color: mix($surfaceVariant, $onSurfaceVariant, 90%); } -menu > menuitem:active { + +menu>menuitem:active { background-color: mix($surfaceVariant, $onSurfaceVariant, 80%); } @@ -93,6 +94,33 @@ tooltip { border: 1px solid $onSurfaceVariant; } +///////////////////////////////////////// +// Emoji Chooser structure +// popover +// ├── box.emoji-searchbar +// │ ╰── entry.search +// ╰── box.emoji-toolbar +// ├── button.image-button.emoji-section +// ├── ... +// ╰── button.image-button.emoji-section + +popover { + @include elevation-border-softer; + padding: 0.681rem; + background: $surfaceVariant; + color: $onSurfaceVariant; + border-radius: 1.159rem; + -gtk-outline-radius: 1.159rem; + + animation-name: appear; + animation-duration: 40ms; + animation-timing-function: ease-out; + animation-iteration-count: 1; +} + + +///////////////////////////////////////// + .configtoggle-box { padding: 0.205rem 0.341rem; border: 0.136rem solid transparent; @@ -145,17 +173,17 @@ tooltip { border: 0.068rem solid $outline; } -.segment-container > *:first-child { +.segment-container>*:first-child { border-top-left-radius: 9999px; border-bottom-left-radius: 9999px; } -.segment-container > * { +.segment-container>* { border-right: 0.068rem solid $outline; padding: 0.341rem 0.682rem; } -.segment-container > *:last-child { +.segment-container>*:last-child { border-right: 0rem solid transparent; border-top-right-radius: 9999px; border-bottom-right-radius: 9999px; @@ -203,4 +231,4 @@ tooltip { .gap-h-15 { min-width: 1.023rem; -} +} \ No newline at end of file diff --git a/.config/ags/scss/_dock.scss b/.config/ags/scss/_dock.scss index d9c0001a9..2965726ab 100644 --- a/.config/ags/scss/_dock.scss +++ b/.config/ags/scss/_dock.scss @@ -22,6 +22,7 @@ .dock-app-icon { min-width: 3.409rem; min-height: 3.409rem; + font-size: 3.409rem; } .dock-separator { diff --git a/.config/ags/scss/_lib_classes.scss b/.config/ags/scss/_lib_classes.scss index 844afc932..6cd3f893b 100644 --- a/.config/ags/scss/_lib_classes.scss +++ b/.config/ags/scss/_lib_classes.scss @@ -55,12 +55,12 @@ margin: 10px; } -.txt-badonkers { +.txt-gigantic { @include mainfont; font-size: 3rem; } -.txt-tiddies { +.txt-massive { @include mainfont; font-size: 2.7273rem; } @@ -127,6 +127,10 @@ @include actiontext; } +.txt-thin { + font-weight: 300; +} + .txt-semibold { font-weight: 500; } diff --git a/.config/ags/scss/_notifications.scss b/.config/ags/scss/_notifications.scss index a5de8e7c5..2eab85052 100644 --- a/.config/ags/scss/_notifications.scss +++ b/.config/ags/scss/_notifications.scss @@ -95,6 +95,7 @@ $notif_surface: $t_background; @include full-rounding; min-width: 3.409rem; min-height: 3.409rem; + font-size: 3.409rem; } .notif-icon-material { diff --git a/.config/ags/scss/_osk.scss b/.config/ags/scss/_osk.scss index ced6bf7ff..eca128c76 100644 --- a/.config/ags/scss/_osk.scss +++ b/.config/ags/scss/_osk.scss @@ -110,3 +110,9 @@ $osk_key_fontsize: 1.091rem; background-color: mix($t_surfaceVariant, $t_onSurfaceVariant, 70%); font-size: $osk_key_fontsize; } + +.osk-key-empty, .osk-key-empty:hover, .osk-key-empty:focus { + min-width: $osk_key_width; + min-height: $osk_key_height; + background-color: transparent; +} diff --git a/.config/ags/scss/_sidebars.scss b/.config/ags/scss/_sidebars.scss index c6b00853b..51a3b5dde 100644 --- a/.config/ags/scss/_sidebars.scss +++ b/.config/ags/scss/_sidebars.scss @@ -445,15 +445,96 @@ $onChatgpt: $onPrimary; .sidebar-module { @include normal-rounding; @include group-padding; - background-color: $t_surface; + background-color: $l_l_t_surfaceVariant; + padding: 0.682rem; } .sidebar-module-btn-arrow { @include full-rounding; @include icon-material; - background-color: $t_surfaceVariant; + background-color: $l_l_t_surfaceVariant; min-width: 1.705rem; min-height: 1.705rem; + + &:hover { + background-color: $hovercolor; + } +} + +.sidebar-module-scripts-button { + @include full-rounding; + @include icon-material; + background-color: $l_l_t_surfaceVariant; + min-width: 1.705rem; + min-height: 1.705rem; + + &:hover { + background-color: $hovercolor; + } + + &:active { + background-color: $activecolor; + } +} + +$colorpicker_rounding: 0.341rem; + +.sidebar-module-colorpicker-wrapper { + padding: 0.341rem; +} + +.sidebar-module-colorpicker-cursorwrapper { + padding: 0.341rem 0.136rem; +} + +.sidebar-module-colorpicker-hue { + min-height: 13.636rem; + min-width: 1.091rem; + border-radius: $colorpicker_rounding; +} + +.sidebar-module-colorpicker-hue-cursor { + background-color: $onBackground; + border: 0.136rem solid $onBackground; + min-height: 0.136rem; + margin-top: -0.136rem; + border-radius: $colorpicker_rounding; +} + +.sidebar-module-colorpicker-saturationandlightness-wrapper { + padding: 0.341rem; +} + +.sidebar-module-colorpicker-saturationandlightness { + min-height: 13.636rem; + min-width: 13.636rem; + border-radius: $colorpicker_rounding; +} + +.sidebar-module-colorpicker-saturationandlightness-cursorwrapper { + padding: 0.341rem; + margin-top: -0.409rem; + margin-left: -0.409rem; +} + +.sidebar-module-colorpicker-saturationandlightness-cursor { + @include full-rounding; + border: 0.136rem solid white; + min-width: 0.682rem; + min-height: 0.682rem; + margin-top: -0.409rem; + margin-left: -0.409rem; +} + +.sidebar-module-colorpicker-result-area { + padding: 0.341rem; +} + +.sidebar-module-colorpicker-result-box { + border-radius: $colorpicker_rounding; + min-width: 2.045rem; + min-height: 0.682rem; + padding: 0.341rem; } .sidebar-chat-apiswitcher { @@ -467,6 +548,7 @@ $onChatgpt: $onPrimary; @include full-rounding; min-width: 2.182rem; min-height: 2.182rem; + font-size: 1.406rem; color: $onSurface; } @@ -642,10 +724,10 @@ $onChatgpt: $onPrimary; .sidebar-chat-welcome-logo { @include full-rounding; @include element_decel; + @include icon-material; min-height: 4.773rem; min-width: 4.773rem; - @include icon-material; - font-size: 2.727rem; + font-size: 3.076rem; background-color: $onBackground; color: $background; } @@ -762,9 +844,7 @@ $waifu_image_overlay_transparency: 0.7; @include full-rounding; min-width: 1.875rem; min-height: 1.875rem; - background-color: rgba(0, - 0, - 0, + background-color: rgba(0, 0, 0, $waifu_image_overlay_transparency ); // Fixed cuz on image color: rgba(255, 255, 255, $waifu_image_overlay_transparency); } @@ -776,4 +856,4 @@ $waifu_image_overlay_transparency: 0.7; .sidebar-waifu-image-action:active { background-color: rgba(60, 60, 60, $waifu_image_overlay_transparency); -} \ No newline at end of file +} diff --git a/.config/ags/scss/main.scss b/.config/ags/scss/main.scss index 83a2e2de3..ea1f378fe 100644 --- a/.config/ags/scss/main.scss +++ b/.config/ags/scss/main.scss @@ -1,8 +1,8 @@ // Reset -* { - all: unset; -} -// *:not(tooltip) { all: unset; } +// * { +// all: unset; +// } +*:not(popover) { all: unset; } // Colors @import './material'; // Material colors diff --git a/.config/ags/services/chatgpt.js b/.config/ags/services/chatgpt.js index 793d1a50f..fcae27f3f 100644 --- a/.config/ags/services/chatgpt.js +++ b/.config/ags/services/chatgpt.js @@ -118,8 +118,8 @@ class ChatGPTService extends Service { _assistantPrompt = true; _messages = []; _cycleModels = true; - _temperature = 0.9; _requestCount = 0; + _temperature = 0.9; _modelIndex = 0; _key = ''; _decoder = new TextDecoder(); diff --git a/.config/ags/services/gemini.js b/.config/ags/services/gemini.js index 02b501765..edbc4ad7d 100644 --- a/.config/ags/services/gemini.js +++ b/.config/ags/services/gemini.js @@ -135,8 +135,8 @@ class GeminiService extends Service { _assistantPrompt = true; _messages = []; _cycleModels = true; - _temperature = 0.9; _requestCount = 0; + _temperature = 0.9; _modelIndex = 0; _key = ''; _decoder = new TextDecoder(); diff --git a/.config/ags/style.css b/.config/ags/style.css index e6f208c4e..8e7efd5d2 100644 --- a/.config/ags/style.css +++ b/.config/ags/style.css @@ -1,4 +1,4 @@ -* { +*:not(popover) { all: unset; } @keyframes flyin-top { @@ -56,11 +56,11 @@ text-shadow: 1px 2px 8px rgba(0, 0, 0, 0.69); margin: 10px; } -.txt-badonkers { +.txt-gigantic { font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif; font-size: 3rem; } -.txt-tiddies { +.txt-massive { font-family: "Rubik", "Geist", "AR One Sans", "Reddit Sans", "Inter", "Roboto", "Ubuntu", "Noto Sans", sans-serif; font-size: 2.7273rem; } @@ -109,6 +109,9 @@ .txt-action { color: #cbc0c1; } +.txt-thin { + font-weight: 300; } + .txt-semibold { font-weight: 500; } @@ -486,6 +489,21 @@ tooltip { color: #d6c1c4; border: 1px solid #d6c1c4; } +popover { + border-top: 1px solid rgba(63, 56, 57, 0.121); + border-left: 1px solid rgba(63, 56, 57, 0.121); + border-right: 1px solid rgba(49, 42, 43, 0.1105); + border-bottom: 1px solid rgba(49, 42, 43, 0.1105); + padding: 0.681rem; + background: #3d3234; + color: #d6c1c4; + border-radius: 1.159rem; + -gtk-outline-radius: 1.159rem; + animation-name: appear; + animation-duration: 40ms; + animation-timing-function: ease-out; + animation-iteration-count: 1; } + .configtoggle-box { padding: 0.205rem 0.341rem; border: 0.136rem solid transparent; } @@ -809,7 +827,8 @@ tooltip { -gtk-outline-radius: 9999px; transition: 300ms cubic-bezier(0, 0.55, 0.45, 1); min-height: 1.032rem; - min-width: 1.032rem; } + min-width: 1.032rem; + font-size: 1.032rem; } .bar-statusicons { border-radius: 9999px; @@ -986,7 +1005,8 @@ tooltip { .dock-app-icon { min-width: 3.409rem; - min-height: 3.409rem; } + min-height: 3.409rem; + font-size: 3.409rem; } .dock-separator { min-width: 0.068rem; @@ -1332,6 +1352,11 @@ tooltip { background-color: rgba(107, 93, 95, 0.31); font-size: 1.091rem; } +.osk-key-empty, .osk-key-empty:hover, .osk-key-empty:focus { + min-width: 2.5rem; + min-height: 2.5rem; + background-color: transparent; } + .sidebar-right { transition: 300ms cubic-bezier(0.1, 1, 0, 1); border-top: 1px solid rgba(171, 160, 161, 0.19); @@ -1709,15 +1734,79 @@ tooltip { border-radius: 1.159rem; -gtk-outline-radius: 1.159rem; padding: 0.341rem; - background-color: rgba(34, 27, 28, 0.31); } + background-color: rgba(61, 50, 52, 0.45); + padding: 0.682rem; } .sidebar-module-btn-arrow { border-radius: 9999px; -gtk-outline-radius: 9999px; font-family: "Material Symbols Rounded", "MaterialSymbolsRounded", "Material Symbols Outlined", "Material Symbols Sharp"; - background-color: rgba(61, 50, 52, 0.31); + background-color: rgba(61, 50, 52, 0.45); min-width: 1.705rem; min-height: 1.705rem; } + .sidebar-module-btn-arrow:hover { + background-color: rgba(128, 128, 128, 0.3); } + +.sidebar-module-scripts-button { + border-radius: 9999px; + -gtk-outline-radius: 9999px; + font-family: "Material Symbols Rounded", "MaterialSymbolsRounded", "Material Symbols Outlined", "Material Symbols Sharp"; + background-color: rgba(61, 50, 52, 0.45); + min-width: 1.705rem; + min-height: 1.705rem; } + .sidebar-module-scripts-button:hover { + background-color: rgba(128, 128, 128, 0.3); } + .sidebar-module-scripts-button:active { + background-color: rgba(128, 128, 128, 0.7); } + +.sidebar-module-colorpicker-wrapper { + padding: 0.341rem; } + +.sidebar-module-colorpicker-cursorwrapper { + padding: 0.341rem 0.136rem; } + +.sidebar-module-colorpicker-hue { + min-height: 13.636rem; + min-width: 1.091rem; + border-radius: 0.341rem; } + +.sidebar-module-colorpicker-hue-cursor { + background-color: #ecdfe0; + border: 0.136rem solid #ecdfe0; + min-height: 0.136rem; + margin-top: -0.136rem; + border-radius: 0.341rem; } + +.sidebar-module-colorpicker-saturationandlightness-wrapper { + padding: 0.341rem; } + +.sidebar-module-colorpicker-saturationandlightness { + min-height: 13.636rem; + min-width: 13.636rem; + border-radius: 0.341rem; } + +.sidebar-module-colorpicker-saturationandlightness-cursorwrapper { + padding: 0.341rem; + margin-top: -0.409rem; + margin-left: -0.409rem; } + +.sidebar-module-colorpicker-saturationandlightness-cursor { + border-radius: 9999px; + -gtk-outline-radius: 9999px; + border: 0.136rem solid white; + min-width: 0.682rem; + min-height: 0.682rem; + margin-top: -0.409rem; + margin-left: -0.409rem; } + +.sidebar-module-colorpicker-result-area { + padding: 0.341rem; } + +.sidebar-module-colorpicker-result-box { + border-radius: 0.341rem; + min-width: 2.045rem; + min-height: 0.682rem; + padding: 0.341rem; } .sidebar-chat-apiswitcher { border-radius: 9999px; @@ -1731,6 +1820,7 @@ tooltip { -gtk-outline-radius: 9999px; min-width: 2.182rem; min-height: 2.182rem; + font-size: 1.406rem; color: #ecdfe0; } .sidebar-chat-apiswitcher-icon-enabled { @@ -1879,10 +1969,10 @@ tooltip { border-radius: 9999px; -gtk-outline-radius: 9999px; transition: 300ms cubic-bezier(0, 0.55, 0.45, 1); + font-family: "Material Symbols Rounded", "MaterialSymbolsRounded", "Material Symbols Outlined", "Material Symbols Sharp"; min-height: 4.773rem; min-width: 4.773rem; - font-family: "Material Symbols Rounded", "MaterialSymbolsRounded", "Material Symbols Outlined", "Material Symbols Sharp"; - font-size: 2.727rem; + font-size: 3.076rem; background-color: #ecdfe0; color: #110d0e; } @@ -2106,7 +2196,8 @@ tooltip { border-radius: 9999px; -gtk-outline-radius: 9999px; min-width: 3.409rem; - min-height: 3.409rem; } + min-height: 3.409rem; + font-size: 3.409rem; } .notif-icon-material { background-color: #5c3f45; diff --git a/.config/ags/widgets/bar/music.js b/.config/ags/widgets/bar/music.js index ae986086a..03f104400 100644 --- a/.config/ags/widgets/bar/music.js +++ b/.config/ags/widgets/bar/music.js @@ -154,7 +154,7 @@ export default () => { className: 'spacing-h-10 margin-left-10', children: [ BarResource('Swap Usage', 'swap_horiz', `free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`), - BarResource('CPU Usage', 'settings_motion_mode', `top -bn1 | grep Cpu | awk '{print $2}'`), + BarResource('CPU Usage', 'settings_motion_mode', `top -bn1 | grep Cpu | sed 's/\\,/\\./g' | awk '{print $2}'`), ] }), setup: (self) => self.hook(Mpris, label => { diff --git a/.config/ags/widgets/bar/system.js b/.config/ags/widgets/bar/system.js index 1ca2fcc9a..631890736 100644 --- a/.config/ags/widgets/bar/system.js +++ b/.config/ags/widgets/bar/system.js @@ -13,6 +13,13 @@ const BATTERY_LOW = 20; const WEATHER_CACHE_FOLDER = `${GLib.get_user_cache_dir()}/ags/weather`; Utils.exec(`mkdir -p ${WEATHER_CACHE_FOLDER}`); +let WEATHER_CITY = ''; +try { + WEATHER_CITY = GLib.getenv('AGS_WEATHER_CITY'); +} catch (e) { + print(e); +} + const BatBatteryProgress = () => { const _updateProgress = (circprog) => { // Set circular progress value circprog.css = `font-size: ${Math.abs(Battery.percent)}px;` @@ -155,15 +162,24 @@ const BatteryModule = () => Stack({ ], setup: (self) => self.poll(900000, async (self) => { const WEATHER_CACHE_PATH = WEATHER_CACHE_FOLDER + '/wttr.in.txt'; - Utils.execAsync('curl ipinfo.io') + const updateWeatherForCity = (city) => execAsync(`curl https://wttr.in/${city}?format=j1`) .then(output => { - return JSON.parse(output)['city'].toLowerCase(); - }) - .then((city) => execAsync(`curl https://wttr.in/${city}?format=j1`) - .then(output => { - const weather = JSON.parse(output); - Utils.writeFile(JSON.stringify(weather), WEATHER_CACHE_PATH) - .catch(print); + const weather = JSON.parse(output); + Utils.writeFile(JSON.stringify(weather), WEATHER_CACHE_PATH) + .catch(print); + const weatherCode = weather.current_condition[0].weatherCode; + const weatherDesc = weather.current_condition[0].weatherDesc[0].value; + const temperature = weather.current_condition[0].temp_C; + const feelsLike = weather.current_condition[0].FeelsLikeC; + const weatherSymbol = WEATHER_SYMBOL[WWO_CODE[weatherCode]]; + self.children[0].label = weatherSymbol; + self.children[1].label = `${temperature}℃ • Feels like ${feelsLike}℃`; + self.tooltipText = weatherDesc; + }).catch((err) => { + try { // Read from cache + const weather = JSON.parse( + Utils.readFile(WEATHER_CACHE_PATH) + ); const weatherCode = weather.current_condition[0].weatherCode; const weatherDesc = weather.current_condition[0].weatherDesc[0].value; const temperature = weather.current_condition[0].temp_C; @@ -172,23 +188,20 @@ const BatteryModule = () => Stack({ self.children[0].label = weatherSymbol; self.children[1].label = `${temperature}℃ • Feels like ${feelsLike}℃`; self.tooltipText = weatherDesc; - }).catch((err) => { - try { // Read from cache - const weather = JSON.parse( - Utils.readFile(WEATHER_CACHE_PATH) - ); - const weatherCode = weather.current_condition[0].weatherCode; - const weatherDesc = weather.current_condition[0].weatherDesc[0].value; - const temperature = weather.current_condition[0].temp_C; - const feelsLike = weather.current_condition[0].FeelsLikeC; - const weatherSymbol = WEATHER_SYMBOL[WWO_CODE[weatherCode]]; - self.children[0].label = weatherSymbol; - self.children[1].label = `${temperature}℃ • Feels like ${feelsLike}℃`; - self.tooltipText = weatherDesc; - } catch (err) { - print(err); - } - })); + } catch (err) { + print(err); + } + }); + if (WEATHER_CITY != '' && WEATHER_CITY != null) { + updateWeatherForCity(WEATHER_CITY); + } + else { + Utils.execAsync('curl ipinfo.io') + .then(output => { + return JSON.parse(output)['city'].toLowerCase(); + }) + .then(updateWeatherForCity); + } }), }) }), diff --git a/.config/ags/widgets/bar/tray.js b/.config/ags/widgets/bar/tray.js index 67d65a833..69c48e23a 100644 --- a/.config/ags/widgets/bar/tray.js +++ b/.config/ags/widgets/bar/tray.js @@ -10,15 +10,8 @@ const SysTrayItem = (item) => Button({ className: 'bar-systray-item', child: Icon({ hpack: 'center', - setup: (self) => { - self.hook(item, (self) => self.icon = item.icon); - Utils.timeout(1, () => { - const styleContext = self.get_parent().get_style_context(); - const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL); - const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); - self.size = Math.max(width, height, 1); // im too lazy to add another box lol - }) - }, + icon: item.icon, + setup: (self) => self.hook(item, (self) => self.icon = item.icon), }), setup: (self) => self .hook(item, (self) => self.tooltipMarkup = item['tooltip-markup']) diff --git a/.config/ags/widgets/bar/workspaces_hyprland.js b/.config/ags/widgets/bar/workspaces_hyprland.js index ae841ebe1..41887c5f8 100644 --- a/.config/ags/widgets/bar/workspaces_hyprland.js +++ b/.config/ags/widgets/bar/workspaces_hyprland.js @@ -21,6 +21,7 @@ const WorkspaceContents = (count = 10) => { attribute: { initialized: false, workspaceMask: 0, + workspaceGroup: 0, updateMask: (self) => { const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * NUM_OF_WORKSPACES_SHOWN; // if (self.attribute.initialized) return; // We only need this to run once @@ -46,6 +47,12 @@ const WorkspaceContents = (count = 10) => { setup: (area) => area .hook(Hyprland.active.workspace, (self) => { self.setCss(`font-size: ${(Hyprland.active.workspace.id - 1) % count + 1}px;`); + const previousGroup = self.attribute.workspaceGroup; + const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / count); + if (currentGroup !== previousGroup) { + self.attribute.updateMask(self); + self.attribute.workspaceGroup = currentGroup; + } }) .hook(Hyprland, (self) => self.attribute.updateMask(self), 'notify::workspaces') .on('draw', Lang.bind(area, (area, cr) => { diff --git a/.config/ags/widgets/dock/dock.js b/.config/ags/widgets/dock/dock.js index f0192d8cc..9a63d3586 100644 --- a/.config/ags/widgets/dock/dock.js +++ b/.config/ags/widgets/dock/dock.js @@ -59,12 +59,6 @@ const AppButton = ({ icon, ...rest }) => Widget.Revealer({ className: 'dock-app-icon', child: Widget.Icon({ icon: icon, - setup: (self) => Utils.timeout(1, () => { - const styleContext = self.get_parent().get_style_context(); - const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL); - const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); - self.size = Math.max(width, height, 1); - }) }), }), overlays: [Widget.Box({ diff --git a/.config/ags/widgets/indicators/main.js b/.config/ags/widgets/indicators/main.js index 698594768..db3fc4c8c 100644 --- a/.config/ags/widgets/indicators/main.js +++ b/.config/ags/widgets/indicators/main.js @@ -1,7 +1,7 @@ import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import Indicator from '../../services/indicator.js'; import IndicatorValues from './indicatorvalues.js'; -// import MusicControls from './musiccontrols.js'; +import MusicControls from './musiccontrols.js'; import ColorScheme from './colorscheme.js'; import NotificationPopups from './notificationpopups.js'; @@ -23,7 +23,7 @@ export default (monitor = 0) => Widget.Window({ css: 'min-height: 2px;', children: [ IndicatorValues(), - // MusicControls(), + MusicControls(), NotificationPopups(), ColorScheme(), ] diff --git a/.config/ags/widgets/indicators/musiccontrols.js b/.config/ags/widgets/indicators/musiccontrols.js index f817eca4a..922d5f5ba 100644 --- a/.config/ags/widgets/indicators/musiccontrols.js +++ b/.config/ags/widgets/indicators/musiccontrols.js @@ -1,4 +1,4 @@ -const { Gio, GLib } = imports.gi; +const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi; 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'; @@ -27,8 +27,9 @@ var lastCoverPath = ''; function isRealPlayer(player) { return ( - !player.busName.startsWith('org.mpris.MediaPlayer2.firefox') && - !player.busName.startsWith('org.mpris.MediaPlayer2.playerctld') + !player.busName.startsWith('org.mpris.MediaPlayer2.firefox') && // Firefox mpris dbus is useless + !player.busName.startsWith('org.mpris.MediaPlayer2.playerctld') && // Doesn't have cover art + !player.busName.endsWith('.mpd') // Non-instance mpd bus ); } @@ -69,7 +70,7 @@ function getTrackfont(player) { return DEFAULT_MUSIC_FONT; } function trimTrackTitle(title) { - if(!title) return ''; + if (!title) return ''; const cleanRegexes = [ /【[^】]*】/, // Touhou n weeb stuff /\[FREE DOWNLOAD\]/, // F-777 @@ -80,7 +81,7 @@ function trimTrackTitle(title) { const TrackProgress = ({ player, ...rest }) => { const _updateProgress = (circprog) => { - const player = Mpris.getPlayer(); + // const player = Mpris.getPlayer(); if (!player) return; // Set circular progress (see definition of AnimatedCircProg for explanation) circprog.css = `font-size: ${Math.max(player.position / player.length * 100, 0)}px;` @@ -122,69 +123,123 @@ const TrackArtists = ({ player, ...rest }) => Label({ }, 'notify::track-artists'), }) -const CoverArt = ({ player, ...rest }) => Box({ - ...rest, - className: 'osd-music-cover', - children: [ - Widget.Overlay({ - child: Box({ // Fallback - className: 'osd-music-cover-fallback', - homogeneous: true, - children: [Label({ - className: 'icon-material txt-hugeass', - label: 'music_note', - })] - }), - overlays: [ // Real - Box({ - attribute: { - 'updateCover': (self) => { - const player = Mpris.getPlayer(); +const CoverArt = ({ player, ...rest }) => { + const fallbackCoverArt = Box({ // Fallback + className: 'osd-music-cover-fallback', + homogeneous: true, + children: [Label({ + className: 'icon-material txt-gigantic txt-thin', + label: 'music_note', + })] + }); + const coverArtDrawingArea = Widget.DrawingArea({ className: 'osd-music-cover-art' }); + const coverArtDrawingAreaStyleContext = coverArtDrawingArea.get_style_context(); + const realCoverArt = Box({ + className: 'osd-music-cover-art', + homogeneous: true, + children: [coverArtDrawingArea], + attribute: { + 'pixbuf': null, + 'showImage': (self, imagePath) => { + const borderRadius = coverArtDrawingAreaStyleContext.get_property('border-radius', Gtk.StateFlags.NORMAL); + const frameHeight = coverArtDrawingAreaStyleContext.get_property('min-height', Gtk.StateFlags.NORMAL); + const frameWidth = coverArtDrawingAreaStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL); + let imageHeight = frameHeight; + let imageWidth = frameWidth; + // Get image dimensions + execAsync(['identify', '-format', '{"w":%w,"h":%h}', imagePath]) + .then((output) => { + const imageDimensions = JSON.parse(output); + const imageAspectRatio = imageDimensions.w / imageDimensions.h; + const displayedAspectRatio = imageWidth / imageHeight; + if (imageAspectRatio >= displayedAspectRatio) { + imageWidth = imageHeight * imageAspectRatio; + } else { + imageHeight = imageWidth / imageAspectRatio; + } + // Real stuff + // TODO: fix memory leak(?) + // if (self.attribute.pixbuf) { + // self.attribute.pixbuf.unref(); + // self.attribute.pixbuf = null; + // } + self.attribute.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(imagePath, imageWidth, imageHeight); - // 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`); - return; - } + coverArtDrawingArea.set_size_request(frameWidth, frameHeight); + coverArtDrawingArea.connect("draw", (widget, cr) => { + // Clip a rounded rectangle area + cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI); + cr.arc(frameWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI); + cr.arc(frameWidth - borderRadius, frameHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI); + cr.arc(borderRadius, frameHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI); + cr.closePath(); + cr.clip(); + // Paint image as bg, centered + Gdk.cairo_set_source_pixbuf(cr, self.attribute.pixbuf, + frameWidth / 2 - imageWidth / 2, + frameHeight / 2 - imageHeight / 2 + ); + cr.paint(); + }); + }).catch(print) + }, + 'updateCover': (self) => { + // const player = Mpris.getPlayer(); // Maybe no need to re-get player.. can't remember why I had this + // 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`); + 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 - self.css = `background-image: url('${coverPath}');`; - } - lastCoverPath = player.coverPath; + 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)) { - self.css = `background-image: url('${coverPath}');`; - App.applyCss(stylePath); - return; - } + // 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; + } - // Generate colors - execAsync(['bash', '-c', - `${App.configDir}/scripts/color_generation/generate_colors_material.py --path '${coverPath}' > ${App.configDir}/scss/_musicmaterial.scss ${lightDark}`]) - .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}');`; - App.applyCss(`${stylePath}`); - }) - .catch(print); - }, - }, - className: 'osd-music-cover-art', - $: [ - [player, (self) => self.attribute.updateCover(self), 'notify::cover-path'] - ], - }) - ] - }) - ], -}) + // Generate colors + execAsync(['bash', '-c', + `${App.configDir}/scripts/color_generation/generate_colors_material.py --path '${coverPath}' > ${App.configDir}/scss/_musicmaterial.scss ${lightDark}`]) + .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}');`; + Utils.timeout(200, () => self.attribute.showImage(self, coverPath)); + App.applyCss(`${stylePath}`); + }) + .catch(print); + }, + }, + setup: (self) => self + .hook(player, (self) => { + self.attribute.updateCover(self); + }, 'notify::cover-path') + , + }); + return Box({ + ...rest, + className: 'osd-music-cover', + children: [ + Widget.Overlay({ + child: fallbackCoverArt, + overlays: [realCoverArt], + }) + ], + }) +} const TrackControls = ({ player, ...rest }) => Widget.Revealer({ revealChild: false, @@ -197,7 +252,7 @@ const TrackControls = ({ player, ...rest }) => Widget.Revealer({ children: [ Button({ className: 'osd-music-controlbtn', - onClicked: () => execAsync('playerctl previous').catch(print), + onClicked: () => player.previous(), child: Label({ className: 'icon-material osd-music-controlbtn-txt', label: 'skip_previous', @@ -205,9 +260,7 @@ const TrackControls = ({ player, ...rest }) => Widget.Revealer({ }), Button({ className: 'osd-music-controlbtn', - onClicked: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"`']) - .catch(print) - , + onClicked: () => player.next(), child: Label({ className: 'icon-material osd-music-controlbtn-txt', label: 'skip_next', @@ -215,8 +268,8 @@ const TrackControls = ({ player, ...rest }) => Widget.Revealer({ }), ], }), - setup: (self) => szelf.hook(Mpris, (self) => { - const player = Mpris.getPlayer(); + setup: (self) => self.hook(Mpris, (self) => { + // const player = Mpris.getPlayer(); if (!player) self.revealChild = false; else @@ -264,7 +317,7 @@ const TrackTime = ({ player, ...rest }) => { children: [ Label({ setup: (self) => self.poll(1000, (self) => { - const player = Mpris.getPlayer(); + // const player = Mpris.getPlayer(); if (!player) return; self.label = lengthStr(player.position); }), @@ -272,7 +325,7 @@ const TrackTime = ({ player, ...rest }) => { Label({ label: '/' }), Label({ setup: (self) => self.hook(Mpris, (self) => { - const player = Mpris.getPlayer(); + // const player = Mpris.getPlayer(); if (!player) return; self.label = lengthStr(player.length); }), @@ -296,7 +349,7 @@ const PlayState = ({ player }) => { overlays: [ Widget.Button({ className: 'osd-music-playstate-btn', - onClicked: () => execAsync('playerctl play-pause').catch(print), + onClicked: () => player.playPause(), child: Widget.Label({ justification: 'center', hpack: 'fill', @@ -313,7 +366,7 @@ const PlayState = ({ player }) => { } const MusicControlsWidget = (player) => Box({ - className: 'osd-music spacing-h-20', + className: 'osd-music spacing-h-20 test', children: [ CoverArt({ player: player, vpack: 'center' }), Box({ @@ -344,35 +397,62 @@ const MusicControlsWidget = (player) => Box({ ] }) -export default () => MarginRevealer({ +export default () => Revealer({ transition: 'slide_down', + transitionDuration: 150, revealChild: false, - showClass: 'osd-show', - hideClass: 'osd-hide', child: Box({ setup: (self) => self.hook(Mpris, box => { - let foundPlayer = false; - + box.children.forEach(child => { + child.destroy(); + child = null; + }); Mpris.players.forEach((player, i) => { if (isRealPlayer(player)) { - foundPlayer = true; - box.children = [MusicControlsWidget(player)]; + const newInstance = MusicControlsWidget(player); + box.add(newInstance); } }); - - if (!foundPlayer) { - const children = box.get_children(); - for (let i = 0; i < children.length; i++) { - const child = children[i]; - child.destroy(); - child = null; - } - return; - } }, 'notify::players'), }), setup: (self) => self.hook(showMusicControls, (revealer) => { - if (showMusicControls.value) revealer.attribute.show(); - else revealer.attribute.hide(); + revealer.revealChild = showMusicControls.value; }), }) + +// export default () => MarginRevealer({ +// transition: 'slide_down', +// revealChild: false, +// showClass: 'osd-show', +// hideClass: 'osd-hide', +// child: Box({ +// setup: (self) => self.hook(Mpris, box => { +// let foundPlayer = false; +// Mpris.players.forEach((player, i) => { +// if (isRealPlayer(player)) { +// foundPlayer = true; +// box.children.forEach(child => { +// child.destroy(); +// child = null; +// }); +// const newInstance = MusicControlsWidget(player); +// box.children = [newInstance]; +// } +// }); + +// if (!foundPlayer) { +// const children = box.get_children(); +// for (let i = 0; i < children.length; i++) { +// const child = children[i]; +// child.destroy(); +// child = null; +// } +// return; +// } +// }, 'notify::players'), +// }), +// setup: (self) => self.hook(showMusicControls, (revealer) => { +// if (showMusicControls.value) revealer.attribute.show(); +// else revealer.attribute.hide(); +// }), +// }) diff --git a/.config/ags/widgets/onscreenkeyboard/onscreenkeyboard.js b/.config/ags/widgets/onscreenkeyboard/onscreenkeyboard.js index e77b91788..d641436d1 100644 --- a/.config/ags/widgets/onscreenkeyboard/onscreenkeyboard.js +++ b/.config/ags/widgets/onscreenkeyboard/onscreenkeyboard.js @@ -1,11 +1,10 @@ const { Gtk } = imports.gi; import App from 'resource:///com/github/Aylur/ags/app.js'; import Widget from 'resource:///com/github/Aylur/ags/widget.js'; -import Service from 'resource:///com/github/Aylur/ags/service.js'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; const { Box, EventBox, Button, Revealer } = Widget; -const { execAsync, exec } = Utils; +const { execAsync } = Utils; import { MaterialIcon } from '../../lib/materialicon.js'; import { separatorLine } from '../../lib/separator.js'; import { defaultOskLayout, oskLayouts } from '../../data/keyboardlayouts.js'; @@ -21,6 +20,18 @@ function releaseAllKeys() { .then(console.log('[OSK] Released all keys')) .catch(print); } +class ShiftMode { + static Off = new ShiftMode('Off'); + static Normal = new ShiftMode('Normal'); + static Locked = new ShiftMode('Locked'); + + constructor(name) { + this.name = name; + } + toString() { + return `ShiftMode.${this.name}`; + } +} var modsPressed = false; const topDecor = Box({ @@ -76,6 +87,10 @@ const keyboardControls = Box({ ] }) +var shiftMode = ShiftMode.Off; +var shiftButton; +var rightShiftButton; +var allButtons = []; const keyboardItself = (kbJson) => { return Box({ vertical: true, @@ -88,14 +103,32 @@ const keyboardItself = (kbJson) => { className: `osk-key osk-key-${key.shape}`, hexpand: ["space", "expand"].includes(key.shape), label: key.label, + attribute: + {key: key}, setup: (button) => { let pressed = false; + allButtons = allButtons.concat(button); if (key.keytype == "normal") { button.connect('pressed', () => { // mouse down execAsync(`ydotool key ${key.keycode}:1`); }); button.connect('clicked', () => { // release execAsync(`ydotool key ${key.keycode}:0`); + + if (shiftMode == ShiftMode.Normal) { + shiftMode = ShiftMode.Off; + if (typeof shiftButton !== 'undefined') { + execAsync(`ydotool key 42:0`); + shiftButton.toggleClassName('osk-key-active', false); + } + if (typeof rightShiftButton !== 'undefined') { + execAsync(`ydotool key 54:0`); + rightShiftButton.toggleClassName('osk-key-active', false); + } + allButtons.forEach(button => { + if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.label; + }) + } }); } else if (key.keytype == "modkey") { @@ -104,14 +137,53 @@ const keyboardItself = (kbJson) => { execAsync(`ydotool key ${key.keycode}:0`); button.toggleClassName('osk-key-active', false); pressed = false; + if (key.keycode == 100) { // Alt Gr button + allButtons.forEach(button => { if (typeof button.attribute.key.labelAlt !== 'undefined') button.label = button.attribute.key.label; }); + } } else { execAsync(`ydotool key ${key.keycode}:1`); button.toggleClassName('osk-key-active', true); - pressed = true; + if (!(key.keycode == 42 || key.keycode == 54)) pressed = true; + else switch (shiftMode.name) { // This toggles the shift button state + case "Off": { + shiftMode = ShiftMode.Normal; + allButtons.forEach(button => { if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.labelShift; }) + if (typeof shiftButton !== 'undefined') { + shiftButton.toggleClassName('osk-key-active', true); + } + if (typeof rightShiftButton !== 'undefined') { + rightShiftButton.toggleClassName('osk-key-active', true); + } + } break; + case "Normal": { + shiftMode = ShiftMode.Locked; + if (typeof shiftButton !== 'undefined') shiftButton.label = key.labelCaps; + if (typeof rightShiftButton !== 'undefined') rightShiftButton.label = key.labelCaps; + } break; + case "Locked": { + shiftMode = ShiftMode.Off; + if (typeof shiftButton !== 'undefined') { + shiftButton.label = key.label; + shiftButton.toggleClassName('osk-key-active', false); + } + if (typeof rightShiftButton !== 'undefined') { + rightShiftButton.label = key.label; + rightShiftButton.toggleClassName('osk-key-active', false); + } + execAsync(`ydotool key ${key.keycode}:0`); + + allButtons.forEach(button => { if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.label; } + )}; + } + if (key.keycode == 100) { // Alt Gr button + allButtons.forEach(button => { if (typeof button.attribute.key.labelAlt !== 'undefined') button.label = button.attribute.key.labelAlt; }); + } modsPressed = true; } }); + if (key.keycode == 42) shiftButton = button; + else if (key.keycode == 54) rightShiftButton = button; } } }) diff --git a/.config/ags/widgets/overview/overview_hyprland.js b/.config/ags/widgets/overview/overview_hyprland.js index 053caba6c..0ce537be5 100644 --- a/.config/ags/widgets/overview/overview_hyprland.js +++ b/.config/ags/widgets/overview/overview_hyprland.js @@ -3,6 +3,7 @@ // - Active ws hook optimization: only update when moving to next group // const { Gdk, Gtk } = imports.gi; +const { Gravity } = imports.gi.Gdk; import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js'; import App from 'resource:///com/github/Aylur/ags/app.js'; import Variable from 'resource:///com/github/Aylur/ags/variable.js'; @@ -48,260 +49,398 @@ function substitute(str) { if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, '-'); // Turn into kebab-case return str; } +export default () => { + const clientMap = new Map(); + let workspaceGroup = 0; + const ContextMenuWorkspaceArray = ({ label, actionFunc, thisWorkspace }) => Widget.MenuItem({ + label: `${label}`, + setup: (menuItem) => { + let submenu = new Gtk.Menu(); + submenu.className = 'menu'; -const ContextMenuWorkspaceArray = ({ label, actionFunc, thisWorkspace }) => Widget.MenuItem({ - label: `${label}`, - setup: (menuItem) => { - let submenu = new Gtk.Menu(); - submenu.className = 'menu'; - - const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN; - const startWorkspace = offset + 1; - const endWorkspace = startWorkspace + NUM_OF_WORKSPACES_SHOWN - 1; - for (let i = startWorkspace; i <= endWorkspace; i++) { - let button = new Gtk.MenuItem({ - label: `Workspace ${i}` - }); - button.connect("activate", () => { - // execAsync([`${onClickBinary}`, `${thisWorkspace}`, `${i}`]).catch(print); - actionFunc(thisWorkspace, i); - }); - submenu.append(button); - } - menuItem.set_reserve_indicator(true); - menuItem.set_submenu(submenu); - } -}) - -const Window = ({ address, at: [x, y], size: [w, h], workspace: { id, name }, class: c, title, xwayland }) => { - const revealInfoCondition = (Math.min(w, h) * OVERVIEW_SCALE > 70); - if (w <= 0 || h <= 0 || (c === '' && title === '')) return null; - if (x + w <= 0) x += (Math.floor(x / SCREEN_WIDTH) * SCREEN_WIDTH); - else if (x < 0) { x = 0; w = x + w; } - if (y + h <= 0) x += (Math.floor(y / SCREEN_HEIGHT) * SCREEN_HEIGHT); - else if (y < 0) { y = 0; h = y + h; } - - if (x >= SCREEN_WIDTH) x %= SCREEN_WIDTH; - else if (x + w > SCREEN_WIDTH) w = SCREEN_WIDTH - x; - if (y >= SCREEN_HEIGHT) y %= SCREEN_HEIGHT; - else if (y + h > SCREEN_HEIGHT) h = SCREEN_HEIGHT - y; - - // title = truncateTitle(title); - return Widget.Button({ - attribute: { x, y }, - className: 'overview-tasks-window', - hpack: 'center', - vpack: 'center', - onClicked: () => { - Hyprland.sendMessage(`dispatch focuswindow address:${address}`); - App.closeWindow('overview'); - }, - onMiddleClickRelease: () => Hyprland.sendMessage(`dispatch closewindow address:${address}`), - onSecondaryClick: (button) => { - button.toggleClassName('overview-tasks-window-selected', true); - const menu = Widget.Menu({ - className: 'menu', - children: [ - Widget.MenuItem({ - child: Widget.Label({ - xalign: 0, - label: "Close (Middle-click)", - }), - onActivate: () => Hyprland.sendMessage(`dispatch closewindow address:${address}`), - }), - ContextMenuWorkspaceArray({ - label: "Dump windows to workspace", - actionFunc: dumpToWorkspace, - thisWorkspace: Number(id) - }), - ContextMenuWorkspaceArray({ - label: "Swap windows with workspace", - actionFunc: swapWorkspace, - thisWorkspace: Number(id) - }), - ], - }); - menu.connect("deactivate", () => { - button.toggleClassName('overview-tasks-window-selected', false); - }) - menu.connect("selection-done", () => { - button.toggleClassName('overview-tasks-window-selected', false); - }) - menu.popup_at_pointer(null); // Show the menu at the pointer's position - }, - child: Widget.Box({ - css: ` - min-width: ${Math.max(w * OVERVIEW_SCALE - 4, 1)}px; - min-height: ${Math.max(h * OVERVIEW_SCALE - 4, 1)}px; - `, - homogeneous: true, - child: Widget.Box({ - vertical: true, - vpack: 'center', - className: 'spacing-v-5', - children: [ - Widget.Icon({ - icon: substitute(c), - size: Math.min(w, h) * OVERVIEW_SCALE / 2.5, - }), - // TODO: Add xwayland tag instead of just having italics - Widget.Revealer({ - transition: 'slide_down', - revealChild: revealInfoCondition, - child: Widget.Label({ - truncate: 'end', - className: `${xwayland ? 'txt txt-italic' : 'txt'}`, - css: ` - font-size: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE / 14.6}px; - margin: 0px ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE / 10}px; - `, - // If the title is too short, include the class - label: (title.length <= 1 ? `${c}: ${title}` : title), - }) - }) - ] - }) - }), - tooltipText: `${c}: ${title}`, - setup: (button) => { - setupCursorHoverGrab(button); - - button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.MOVE); - button.drag_source_set_icon_name(substitute(c)); - // button.drag_source_set_icon_gicon(icon); - - button.connect('drag-begin', (button) => { // On drag start, add the dragging class - button.toggleClassName('overview-tasks-window-dragging', true); - }); - button.connect('drag-data-get', (_w, _c, data) => { // On drag finish, give address - data.set_text(address, address.length); - button.toggleClassName('overview-tasks-window-dragging', false); - }); - }, - }); -} - -const Workspace = (index) => { - const fixed = Gtk.Fixed.new(); - // const clientMap = new Map(); - const WorkspaceNumber = (index) => Widget.Label({ - className: 'overview-tasks-workspace-number', - label: `${index}`, - css: ` - margin: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE * OVERVIEW_WS_NUM_MARGIN_SCALE}px; - font-size: ${SCREEN_HEIGHT * OVERVIEW_SCALE * OVERVIEW_WS_NUM_SCALE}px; - `, - }) - const widget = Widget.Box({ - className: 'overview-tasks-workspace', - vpack: 'center', - css: ` - min-width: ${SCREEN_WIDTH * OVERVIEW_SCALE}px; - min-height: ${SCREEN_HEIGHT * OVERVIEW_SCALE}px; - `, - children: [Widget.EventBox({ - hexpand: true, - vexpand: true, - onPrimaryClick: () => { - Hyprland.sendMessage(`dispatch workspace ${index}`) - App.closeWindow('overview'); - }, - setup: (eventbox) => { - eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY); - eventbox.connect('drag-data-received', (_w, _c, _x, _y, data) => { - Hyprland.sendMessage(`dispatch movetoworkspacesilent ${index},address:${data.get_text()}`) + const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN; + const startWorkspace = offset + 1; + const endWorkspace = startWorkspace + NUM_OF_WORKSPACES_SHOWN - 1; + for (let i = startWorkspace; i <= endWorkspace; i++) { + let button = new Gtk.MenuItem({ + label: `Workspace ${i}` + }); + button.connect("activate", () => { + // execAsync([`${onClickBinary}`, `${thisWorkspace}`, `${i}`]).catch(print); + actionFunc(thisWorkspace, i); overviewTick.setValue(!overviewTick.value); }); - }, - child: fixed, - })], - }); - widget.clear = () => { - fixed.get_children().forEach(ch => ch.destroy()); - const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN; - fixed.put(WorkspaceNumber(offset + index), 0, 0); - } - widget.set = (clientJson) => { - // if(clientMap.get(clientJson.address)) clientMap.get(clientJson.address).destroy(); - const newWindow = Window(clientJson); - if (newWindow === null) return; - // clientMap.set(clientJson.address, newWindow); - fixed.put(newWindow, - Math.max(0, newWindow.attribute.x * OVERVIEW_SCALE), - Math.max(0, newWindow.attribute.y * OVERVIEW_SCALE) - ); - }; - // widget.unset = (clientAddress) => { - // if(clientMap.get(clientAddress)) { - // clientMap.get(clientAddress).destroy(); - // clientMap.delete(clientAddress); - // } - // }; - widget.show = () => { - fixed.show_all(); - } - return widget; -}; - -const arr = (s, n) => { - const array = []; - for (let i = 0; i < n; i++) - array.push(s + i); - - return array; -}; - -const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) => Widget.Box({ - children: arr(startWorkspace, workspaces).map(Workspace), - attribute: { - update: (box) => { - const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN; - if (!App.getWindow(windowName).visible) return; - execAsync('hyprctl -j clients').then(clients => { - const allClients = JSON.parse(clients); - const kids = box.get_children(); - kids.forEach(kid => kid.clear()); - for (let i = 0; i < allClients.length; i++) { - const client = allClients[i]; - if (offset + startWorkspace <= client.workspace.id && - client.workspace.id <= offset + startWorkspace + workspaces) { - kids[client.workspace.id - (offset + startWorkspace)] - ?.set(client); - } - } - kids.forEach(kid => kid.show()); - - }).catch(print); + submenu.append(button); + } + menuItem.set_reserve_indicator(true); + menuItem.set_submenu(submenu); } - }, - setup: (box) => box - .hook(overviewTick, (box) => box.attribute.update(box)) - .hook(Hyprland, (box, clientAddress) => { - box.attribute.update(box) - }, 'client-removed') - .hook(Hyprland, (box, clientAddress) => { - box.attribute.update(box); - }, 'client-added') - .hook(Hyprland.active.workspace, (box) => box.attribute.update(box)) - .hook(App, (box, name, visible) => { // Update on open - if (name == 'overview' && visible) box.attribute.update(box); + }) + + const Window = ({ address, at: [x, y], size: [w, h], workspace: { id, name }, class: c, title, xwayland }, screenCoords) => { + const revealInfoCondition = (Math.min(w, h) * OVERVIEW_SCALE > 70); + if (w <= 0 || h <= 0 || (c === '' && title === '')) return null; + // Non-primary monitors + if (screenCoords.x != 0) x -= screenCoords.x; + if (screenCoords.y != 0) y -= screenCoords.y; + // Other offscreen adjustments + if (x + w <= 0) x += (Math.floor(x / SCREEN_WIDTH) * SCREEN_WIDTH); + else if (x < 0) { w = x + w; x = 0; } + if (y + h <= 0) x += (Math.floor(y / SCREEN_HEIGHT) * SCREEN_HEIGHT); + else if (y < 0) { h = y + h; y = 0; } + // Truncate if offscreen + if (x + w > SCREEN_WIDTH) w = SCREEN_WIDTH - x; + if (y + h > SCREEN_HEIGHT) h = SCREEN_HEIGHT - y; + + const appIcon = Widget.Icon({ + icon: substitute(c), + size: Math.min(w, h) * OVERVIEW_SCALE / 2.5, + }); + return Widget.Button({ + attribute: { + address, x, y, w, h, ws: id, + updateIconSize: (self) => { + appIcon.size = Math.min(self.attribute.w, self.attribute.h) * OVERVIEW_SCALE / 2.5; + }, + }, + className: 'overview-tasks-window', + hpack: 'start', + vpack: 'start', + css: ` + margin-left: ${Math.round(x * OVERVIEW_SCALE)}px; + margin-top: ${Math.round(y * OVERVIEW_SCALE)}px; + margin-right: -${Math.round((x + w) * OVERVIEW_SCALE)}px; + margin-bottom: -${Math.round((y + h) * OVERVIEW_SCALE)}px; + `, + onClicked: (self) => { + Hyprland.sendMessage(`dispatch focuswindow address:${address}`); + App.closeWindow('overview'); + }, + onMiddleClickRelease: () => Hyprland.sendMessage(`dispatch closewindow address:${address}`), + onSecondaryClick: (button) => { + button.toggleClassName('overview-tasks-window-selected', true); + const menu = Widget.Menu({ + className: 'menu', + children: [ + Widget.MenuItem({ + child: Widget.Label({ + xalign: 0, + label: "Close (Middle-click)", + }), + onActivate: () => Hyprland.sendMessage(`dispatch closewindow address:${address}`), + }), + ContextMenuWorkspaceArray({ + label: "Dump windows to workspace", + actionFunc: dumpToWorkspace, + thisWorkspace: Number(id) + }), + ContextMenuWorkspaceArray({ + label: "Swap windows with workspace", + actionFunc: swapWorkspace, + thisWorkspace: Number(id) + }), + ], + }); + menu.connect("deactivate", () => { + button.toggleClassName('overview-tasks-window-selected', false); + }) + menu.connect("selection-done", () => { + button.toggleClassName('overview-tasks-window-selected', false); + }) + menu.popup_at_widget(button.get_parent(), Gravity.SOUTH, Gravity.NORTH, null); // Show menu below the button + button.connect("destroy", () => menu.destroy()); + }, + child: Widget.Box({ + homogeneous: true, + child: Widget.Box({ + vertical: true, + vpack: 'center', + className: 'spacing-v-5', + children: [ + appIcon, + // TODO: Add xwayland tag instead of just having italics + // Widget.Revealer({ + // transition: 'slide_down', + // revealChild: revealInfoCondition, + // child: Widget.Label({ + // truncate: 'end', + // className: `${xwayland ? 'txt txt-italic' : 'txt'}`, + // css: ` + // font-size: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE / 14.6}px; + // margin: 0px ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE / 10}px; + // `, + // // If the title is too short, include the class + // label: (title.length <= 1 ? `${c}: ${title}` : title), + // }) + // }) + ] + }) + }), + tooltipText: `${c}: ${title}`, + setup: (button) => { + setupCursorHoverGrab(button); + + button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.MOVE); + button.drag_source_set_icon_name(substitute(c)); + // button.drag_source_set_icon_gicon(icon); + + button.connect('drag-begin', (button) => { // On drag start, add the dragging class + button.toggleClassName('overview-tasks-window-dragging', true); + }); + button.connect('drag-data-get', (_w, _c, data) => { // On drag finish, give address + data.set_text(address, address.length); + button.toggleClassName('overview-tasks-window-dragging', false); + }); + }, + }); + } + + const Workspace = (index) => { + // const fixed = Widget.Fixed({ + // attribute: { + // put: (widget, x, y) => { + // fixed.put(widget, x, y); + // }, + // move: (widget, x, y) => { + // fixed.move(widget, x, y); + // }, + // } + // }); + const fixed = Widget.Box({ + attribute: { + put: (widget, x, y) => { + if (!widget.attribute) return; + // Note: x and y are already multiplied by OVERVIEW_SCALE + const newCss = ` + margin-left: ${Math.round(x)}px; + margin-top: ${Math.round(y)}px; + margin-right: -${Math.round(x + (widget.attribute.w * OVERVIEW_SCALE))}px; + margin-bottom: -${Math.round(y + (widget.attribute.h * OVERVIEW_SCALE))}px; + `; + widget.css = newCss; + fixed.pack_start(widget, false, false, 0); + }, + move: (widget, x, y) => { + if (!widget) return; + if (!widget.attribute) return; + // Note: x and y are already multiplied by OVERVIEW_SCALE + const newCss = ` + margin-left: ${Math.round(x)}px; + margin-top: ${Math.round(y)}px; + margin-right: -${Math.round(x + (widget.attribute.w * OVERVIEW_SCALE))}px; + margin-bottom: -${Math.round(y + (widget.attribute.h * OVERVIEW_SCALE))}px; + `; + widget.css = newCss; + }, + } }) - , -}); + const WorkspaceNumber = (index) => Widget.Label({ + className: 'overview-tasks-workspace-number', + label: `${index}`, + css: ` + margin: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE * OVERVIEW_WS_NUM_MARGIN_SCALE}px; + font-size: ${SCREEN_HEIGHT * OVERVIEW_SCALE * OVERVIEW_WS_NUM_SCALE}px; + `, + }) + const widget = Widget.Box({ + className: 'overview-tasks-workspace', + vpack: 'center', + css: ` + min-width: ${SCREEN_WIDTH * OVERVIEW_SCALE}px; + min-height: ${SCREEN_HEIGHT * OVERVIEW_SCALE}px; + `, + children: [Widget.EventBox({ + hexpand: true, + vexpand: true, + onPrimaryClick: () => { + Hyprland.sendMessage(`dispatch workspace ${index}`) + App.closeWindow('overview'); + }, + setup: (eventbox) => { + eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY); + eventbox.connect('drag-data-received', (_w, _c, _x, _y, data) => { + Hyprland.sendMessage(`dispatch movetoworkspacesilent ${index},address:${data.get_text()}`) + overviewTick.setValue(!overviewTick.value); + }); + }, + child: fixed, + })], + }); + const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN; + fixed.attribute.put(WorkspaceNumber(offset + index), 0, 0); + widget.clear = () => { + const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN; + clientMap.forEach((client, address) => { + if (!client || client.ws !== offset + index) return; + client.destroy(); + client = null; + clientMap.delete(address); + }); + } + widget.set = (clientJson, screenCoords) => { + let c = clientMap.get(clientJson.address); + if (c) { + if (c.attribute?.ws !== clientJson.workspace.id) { + c.destroy(); + c = null; + clientMap.delete(clientJson.address); + } + else if (c) { + c.attribute.w = clientJson.size[0]; + c.attribute.h = clientJson.size[1]; + c.attribute.updateIconSize(c); + fixed.attribute.move(c, + Math.max(0, clientJson.at[0] * OVERVIEW_SCALE), + Math.max(0, clientJson.at[1] * OVERVIEW_SCALE) + ); + return; + } + } + const newWindow = Window(clientJson, screenCoords); + if (newWindow === null) return; + // clientMap.set(clientJson.address, newWindow); + fixed.attribute.put(newWindow, + Math.max(0, newWindow.attribute.x * OVERVIEW_SCALE), + Math.max(0, newWindow.attribute.y * OVERVIEW_SCALE) + ); + clientMap.set(clientJson.address, newWindow); + }; + widget.unset = (clientAddress) => { + const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN; + let c = clientMap.get(clientAddress); + if (!c) return; + c.destroy(); + c = null; + clientMap.delete(clientAddress); + }; + widget.show = () => { + fixed.show_all(); + } + return widget; + }; + const arr = (s, n) => { + const array = []; + for (let i = 0; i < n; i++) + array.push(s + i); -export default () => Widget.Revealer({ - revealChild: true, - transition: 'slide_down', - transitionDuration: 200, - child: Widget.Box({ - vertical: true, - className: 'overview-tasks', - children: Array.from({ length: NUM_OF_WORKSPACE_ROWS }, (_, index) => - OverviewRow({ - startWorkspace: 1 + index * NUM_OF_WORKSPACE_COLS, - workspaces: NUM_OF_WORKSPACE_COLS, - }) - ) - }), -}); + return array; + }; + + const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) => Widget.Box({ + children: arr(startWorkspace, workspaces).map(Workspace), + attribute: { + monitorMap: [], + getMonitorMap: (box) => { + execAsync('hyprctl -j monitors').then(monitors => { + box.attribute.monitorMap = JSON.parse(monitors).reduce((acc, item) => { + acc[item.id] = { x: item.x, y: item.y }; + return acc; + }, {}); + }); + }, + update: (box) => { + const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN; + if (!App.getWindow(windowName).visible) return; + Hyprland.sendMessage('j/clients').then(clients => { + const allClients = JSON.parse(clients); + const kids = box.get_children(); + kids.forEach(kid => kid.clear()); + // console.log('----------------------------'); + for (let i = 0; i < allClients.length; i++) { + const client = allClients[i]; + const childID = client.workspace.id - (offset + startWorkspace); + if (offset + startWorkspace <= client.workspace.id && + client.workspace.id <= offset + startWorkspace + workspaces) { + const screenCoords = box.attribute.monitorMap[client.monitor]; + if (kids[childID]) { + kids[childID].set(client, screenCoords); + } + continue; + } + // const modID = client.workspace.id % NUM_OF_WORKSPACES_SHOWN; + // console.log(`[${startWorkspace} -> ${startWorkspace + workspaces - 1}] ? (${client.workspace.id} == ${modID})`); + // // console.log(`[${startWorkspace} -> ${startWorkspace + workspaces}] ? (${modID})`); + // if (startWorkspace <= modID && modID < startWorkspace + workspaces) { + // console.log('i care'); + // const clientWidget = clientMap.get(client.address); + // console.log(childID, kids[childID], clientWidget); + // if (kids[childID] && clientWidget) { + // console.log('hmm remove', clientWidget.attribute) + // kids[childID].remove(clientWidget); + // } + // } + } + kids.forEach(kid => kid.show()); + }).catch(print); + }, + updateWorkspace: (box, id) => { + const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN; + if (!( // Not in range, ignore + offset + startWorkspace <= id && + id <= offset + startWorkspace + workspaces + )) return; + // if (!App.getWindow(windowName).visible) return; + Hyprland.sendMessage('j/clients').then(clients => { + const allClients = JSON.parse(clients); + const kids = box.get_children(); + for (let i = 0; i < allClients.length; i++) { + const client = allClients[i]; + if (client.workspace.id != id) continue; + const screenCoords = box.attribute.monitorMap[client.monitor]; + kids[id - (offset + startWorkspace)]?.set(client, screenCoords); + } + kids[id - (offset + startWorkspace)]?.show(); + }).catch(print); + }, + }, + setup: (box) => { + box.attribute.getMonitorMap(box); + box + .hook(overviewTick, (box) => box.attribute.update(box)) + .hook(Hyprland, (box, clientAddress) => { + const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN; + const kids = box.get_children(); + const client = Hyprland.getClient(clientAddress); + if (!client) return; + const id = client.workspace.id; + + box.attribute.updateWorkspace(box, id); + kids[id - (offset + startWorkspace)]?.unset(clientAddress); + }, 'client-removed') + .hook(Hyprland, (box, clientAddress) => { + const client = Hyprland.getClient(clientAddress); + if (!client) return; + box.attribute.updateWorkspace(box, client.workspace.id); + }, 'client-added') + .hook(Hyprland.active.workspace, (box) => { + const previousGroup = box.attribute.workspaceGroup; + const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN); + if (currentGroup !== previousGroup) { + box.attribute.update(box); + workspaceGroup = currentGroup; + } + // box.attribute.update(box); + }) + .hook(App, (box, name, visible) => { // Update on open + if (name == 'overview' && visible) box.attribute.update(box); + }) + }, + }); + + return Widget.Revealer({ + revealChild: true, + transition: 'slide_down', + transitionDuration: 200, + child: Widget.Box({ + vertical: true, + className: 'overview-tasks', + children: Array.from({ length: NUM_OF_WORKSPACE_ROWS }, (_, index) => + OverviewRow({ + startWorkspace: 1 + index * NUM_OF_WORKSPACE_COLS, + workspaces: NUM_OF_WORKSPACE_COLS, + }) + ) + }), + }); +} \ No newline at end of file diff --git a/.config/ags/widgets/overview/searchbuttons.js b/.config/ags/widgets/overview/searchbuttons.js index 8ea12c887..15130ad05 100644 --- a/.config/ags/widgets/overview/searchbuttons.js +++ b/.config/ags/widgets/overview/searchbuttons.js @@ -38,12 +38,6 @@ export const DirectoryButton = ({ parentPath, name, type, icon }) => { homogeneous: true, child: Widget.Icon({ icon: icon, - setup: (self) => Utils.timeout(1, () => { - const styleContext = self.get_parent().get_style_context(); - const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL); - const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); - self.size = Math.max(width, height, 1); - }) }), }), Widget.Label({ @@ -112,12 +106,6 @@ export const DesktopEntryButton = (app) => { homogeneous: true, child: Widget.Icon({ icon: app.iconName, - setup: (self) => Utils.timeout(1, () => { - const styleContext = self.get_parent().get_style_context(); - const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL); - const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); - self.size = Math.max(width, height, 1); - }) }), }), Widget.Label({ diff --git a/.config/ags/widgets/overview/windowcontent.js b/.config/ags/widgets/overview/windowcontent.js index 68b9d34a4..5a9dd8882 100644 --- a/.config/ags/widgets/overview/windowcontent.js +++ b/.config/ags/widgets/overview/windowcontent.js @@ -1,4 +1,4 @@ -const { Gtk } = imports.gi; +const { Gdk, Gtk } = imports.gi; 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'; @@ -76,7 +76,7 @@ export const SearchAndWindows = () => { child: Widget.Label({ className: 'overview-search-prompt txt-small txt', label: 'Type to search' - }) + }), }); const entryIconRevealer = Widget.Revealer({ @@ -217,7 +217,10 @@ export const SearchAndWindows = () => { entry, Widget.Box({ className: 'overview-search-icon-box', - setup: box => box.pack_start(entryPromptRevealer, true, true, 0), + setup: (box) => { + box.pack_start(entryPromptRevealer, true, true, 0) + // enableClickthrough(box); + }, }), entryIcon, ] @@ -233,10 +236,31 @@ export const SearchAndWindows = () => { } }) .on('key-press-event', (widget, event) => { // Typing - if (event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 && widget != entry) { - Utils.timeout(1, () => entry.grab_focus()); - entry.set_text(entry.text + String.fromCharCode(event.get_keyval()[1])); - entry.set_position(-1); + const keyval = event.get_keyval()[1]; + const modstate = event.get_state()[1]; + if (modstate & Gdk.ModifierType.CONTROL_MASK) { // Ctrl held + if (keyval == Gdk.KEY_b) + entry.set_position(Math.max(entry.get_position() - 1, 0)); + else if (keyval == Gdk.KEY_f) + entry.set_position(Math.min(entry.get_position() + 1, entry.get_text().length)); + else if (keyval == Gdk.KEY_n) { // simulate Down arrow + entry.get_root_window().simulate_key_press(Gdk.KEY_Down, Gdk.ModifierType.NONE); + // entry.get_root_window().simulate_key_release(Gdk.KEY_Down, Gdk.ModifierType.NONE); + } + else if (keyval == Gdk.KEY_k) { // Delete to end + const text = entry.get_text(); + const pos = entry.get_position(); + const newText = text.slice(0, pos); + entry.set_text(newText); + entry.set_position(newText.length); + } + } + else { // Ctrl not held + if (keyval >= 32 && keyval <= 126 && widget != entry) { + Utils.timeout(1, () => entry.grab_focus()); + entry.set_text(entry.text + String.fromCharCode(keyval)); + entry.set_position(-1); + } } }) , diff --git a/.config/ags/widgets/sideleft/apis/ai_chatmessage.js b/.config/ags/widgets/sideleft/apis/ai_chatmessage.js index 66b589cd1..d2cf8d20a 100644 --- a/.config/ags/widgets/sideleft/apis/ai_chatmessage.js +++ b/.config/ags/widgets/sideleft/apis/ai_chatmessage.js @@ -44,6 +44,7 @@ function copyToClipboard(text) { function substituteLang(str) { const subs = [ { from: 'javascript', to: 'js' }, + { from: 'bash', to: 'sh' }, ]; for (const { from, to } of subs) { @@ -58,7 +59,7 @@ const HighlightedCode = (content, lang) => { const buffer = new GtkSource.Buffer(); const sourceView = new GtkSource.View({ buffer: buffer, - wrap_mode: Gtk.WrapMode.WORD + wrap_mode: Gtk.WrapMode.NONE }); const langManager = GtkSource.LanguageManager.get_default(); let displayLang = langManager.get_language(substituteLang(lang)); // Set your preferred language diff --git a/.config/ags/widgets/sideleft/apis/chatgpt.js b/.config/ags/widgets/sideleft/apis/chatgpt.js index 91de66250..16e54b161 100644 --- a/.config/ags/widgets/sideleft/apis/chatgpt.js +++ b/.config/ags/widgets/sideleft/apis/chatgpt.js @@ -19,14 +19,6 @@ export const chatGPTTabIcon = Icon({ hpack: 'center', className: 'sidebar-chat-apiswitcher-icon', icon: `openai-symbolic`, - setup: (self) => Utils.timeout(513, () => { // stupid condition race - const styleContext = self.get_style_context(); - const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL); - const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); - // console.log(Math.round(Math.max(width, height, 1))); - self.size = Math.max(width, height, 1) * 116 / 180; - // ↑ Why such a specific proportion? See https://openai.com/brand#logos - }) }); const ChatGPTInfo = () => { @@ -34,14 +26,6 @@ const ChatGPTInfo = () => { hpack: 'center', className: 'sidebar-chat-welcome-logo', icon: `openai-symbolic`, - setup: (self) => Utils.timeout(513, () => { // stupid condition race - const styleContext = self.get_style_context(); - const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL); - const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); - // console.log(Math.round(Math.max(width, height, 1))); - self.size = Math.max(width, height, 1) * 116 / 180; - // ↑ Why such a specific proportion? See https://openai.com/brand#logos - }) }); return Box({ vertical: true, diff --git a/.config/ags/widgets/sideleft/apis/gemini.js b/.config/ags/widgets/sideleft/apis/gemini.js index 0931c9730..6d9da681d 100644 --- a/.config/ags/widgets/sideleft/apis/gemini.js +++ b/.config/ags/widgets/sideleft/apis/gemini.js @@ -20,12 +20,6 @@ export const geminiTabIcon = Icon({ hpack: 'center', className: 'sidebar-chat-apiswitcher-icon', icon: `google-gemini-symbolic`, - setup: (self) => Utils.timeout(513, () => { // stupid condition race - const styleContext = self.get_style_context(); - const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL); - const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); - self.size = Math.max(width, height, 1) * 116 / 180; - }) }) const GeminiInfo = () => { @@ -33,12 +27,6 @@ const GeminiInfo = () => { hpack: 'center', className: 'sidebar-chat-welcome-logo', icon: `google-gemini-symbolic`, - setup: (self) => Utils.timeout(513, () => { // stupid condition race - const styleContext = self.get_style_context(); - const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL); - const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); - self.size = Math.max(width, height, 1) * 116 / 180; - }) }); return Box({ vertical: true, diff --git a/.config/ags/widgets/sideleft/apis/waifu.js b/.config/ags/widgets/sideleft/apis/waifu.js index ef31b6d66..94da96032 100644 --- a/.config/ags/widgets/sideleft/apis/waifu.js +++ b/.config/ags/widgets/sideleft/apis/waifu.js @@ -417,6 +417,14 @@ export const sendMessage = (text) => { true )); } + else if (text.startsWith('/place')) { + const newImage = WaifuImage(['/place']); + waifuContent.add(newImage); + Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage.attribute.update( + DummyTag(400, 600, 'https://placewaifu.com/image/400/600', '#F0A235'), + true + )); + } } else WaifuService.fetch(text); diff --git a/.config/ags/widgets/sideleft/apiwidgets.js b/.config/ags/widgets/sideleft/apiwidgets.js index 4ac117f53..f754fae37 100644 --- a/.config/ags/widgets/sideleft/apiwidgets.js +++ b/.config/ags/widgets/sideleft/apiwidgets.js @@ -13,19 +13,12 @@ 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'; import { waifuView, waifuCommands, sendMessage as waifuSendMessage, waifuTabIcon } from './apis/waifu.js'; -const TextView = Widget.subclass(Gtk.TextView, "AgsTextView"); +import { enableClickthrough } from '../../lib/roundedcorner.js'; +const TextView = Widget.subclass(Gtk.TextView, "AgsTextView"); const EXPAND_INPUT_THRESHOLD = 30; const APIS = [ - { - name: 'Assistant (ChatGPT 3.5)', - sendCommand: chatGPTSendMessage, - contentWidget: chatGPTView, - commandBar: chatGPTCommands, - tabIcon: chatGPTTabIcon, - placeholderText: 'Message ChatGPT...', - }, { name: 'Assistant (Gemini Pro)', sendCommand: geminiSendMessage, @@ -34,6 +27,14 @@ const APIS = [ tabIcon: geminiTabIcon, placeholderText: 'Message Gemini...', }, + { + name: 'Assistant (ChatGPT 3.5)', + sendCommand: chatGPTSendMessage, + contentWidget: chatGPTView, + commandBar: chatGPTCommands, + tabIcon: chatGPTTabIcon, + placeholderText: 'Message ChatGPT...', + }, { name: 'Waifus', sendCommand: waifuSendMessage, @@ -141,6 +142,7 @@ const chatPlaceholderRevealer = Revealer({ transition: 'crossfade', transitionDuration: 200, child: chatPlaceholder, + setup: enableClickthrough, }); const textboxArea = Box({ // Entry area @@ -159,12 +161,20 @@ const textboxArea = Box({ // Entry area const apiContentStack = Stack({ vexpand: true, transition: 'slide_left_right', - items: APIS.map(api => [api.name, api.contentWidget]), + transitionDuration: 160, + children: APIS.reduce((acc, api) => { + acc[api.name] = api.contentWidget; + return acc; + }, {}), }) const apiCommandStack = Stack({ transition: 'slide_up_down', - items: APIS.map(api => [api.name, api.commandBar]), + transitionDuration: 160, + children: APIS.reduce((acc, api) => { + acc[api.name] = api.commandBar; + return acc; + }, {}), }) function switchToTab(id) { diff --git a/.config/ags/widgets/sideleft/module.js b/.config/ags/widgets/sideleft/module.js deleted file mode 100644 index 319b5a35c..000000000 --- a/.config/ags/widgets/sideleft/module.js +++ /dev/null @@ -1,30 +0,0 @@ -import Widget from 'resource:///com/github/Aylur/ags/widget.js'; -const { Box, Button, Label } = Widget; - -export const SidebarModule = ({ - name, - child -}) => { - return Box({ - className: 'sidebar-module', - vertical: true, - children: [ - Button({ - child: Box({ - children: [ - Label({ - className: 'txt-small txt', - label: `${name}`, - }), - Box({ - hexpand: true, - }), - Label({ - className: 'sidebar-module-btn-arrow', - }) - ] - }) - }) - ] - }); -} \ No newline at end of file diff --git a/.config/ags/widgets/sideleft/quickscripts.js b/.config/ags/widgets/sideleft/quickscripts.js deleted file mode 100644 index 13bf9c1a1..000000000 --- a/.config/ags/widgets/sideleft/quickscripts.js +++ /dev/null @@ -1,11 +0,0 @@ -import Widget from 'resource:///com/github/Aylur/ags/widget.js'; -import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; -const { execAsync, exec } = Utils; -const { Box, Button, EventBox, Label, Scrollable } = Widget; -import { SidebarModule } from './module.js'; - -export const QuickScripts = () => SidebarModule({ - name: 'Quick scripts', - child: Box({ - }) -}) \ No newline at end of file diff --git a/.config/ags/widgets/sideleft/sideleft.js b/.config/ags/widgets/sideleft/sideleft.js index fadf7cad0..d67d87ade 100644 --- a/.config/ags/widgets/sideleft/sideleft.js +++ b/.config/ags/widgets/sideleft/sideleft.js @@ -30,7 +30,11 @@ let currentTabId = 0; export const contentStack = Stack({ vexpand: true, transition: 'slide_left_right', - items: contents.map(item => [item.name, item.content]), + transitionDuration: 160, + children: contents.reduce((acc, item) => { + acc[item.name] = item.content; + return acc; + }, {}), }) function switchToTab(id) { diff --git a/.config/ags/widgets/sideleft/toolbox.js b/.config/ags/widgets/sideleft/toolbox.js index 0c14beded..9ecb98b92 100644 --- a/.config/ags/widgets/sideleft/toolbox.js +++ b/.config/ags/widgets/sideleft/toolbox.js @@ -2,15 +2,18 @@ import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; const { Box, Button, EventBox, Label, Revealer, Scrollable, Stack } = Widget; const { execAsync, exec } = Utils; -import { QuickScripts } from './quickscripts.js'; +import QuickScripts from './tools/quickscripts.js'; +import ColorPicker from './tools/colorpicker.js'; export default Scrollable({ hscroll: "never", vscroll: "automatic", child: Box({ vertical: true, + className: 'spacing-v-10', children: [ - // QuickScripts(), + QuickScripts(), + ColorPicker(), ] }) }); diff --git a/.config/ags/widgets/sideleft/tools/color.js b/.config/ags/widgets/sideleft/tools/color.js new file mode 100644 index 000000000..f93b56b57 --- /dev/null +++ b/.config/ags/widgets/sideleft/tools/color.js @@ -0,0 +1,199 @@ +// It's weird, I know +const { Gio, GLib } = imports.gi; +import Service from 'resource:///com/github/Aylur/ags/service.js'; +import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; +const { exec, execAsync } = Utils; + +const clamp = (num, min, max) => Math.min(Math.max(num, min), max); + +export class ColorPickerSelection extends Service { + static { + Service.register(this, { + 'picked': [], + 'assigned': ['int'], + 'hue': [], + 'sl': [], + }); + } + + _hue = 198; + _xAxis = 94; + _yAxis = 80; + + get hue() { return this._hue; } + set hue(value) { + this._hue = clamp(value, 0, 360); + this.emit('hue'); + this.emit('picked'); + this.emit('changed'); + } + get xAxis() { return this._xAxis; } + set xAxis(value) { + this._xAxis = clamp(value, 0, 100); + this.emit('sl'); + this.emit('picked'); + this.emit('changed'); + } + get yAxis() { return this._yAxis; } + set yAxis(value) { + this._yAxis = clamp(value, 0, 100); + this.emit('sl'); + this.emit('picked'); + this.emit('changed'); + } + setColorFromHex(hexString, id) { + const hsl = hexToHSL(hexString); + this._hue = hsl.hue; + this._xAxis = hsl.saturation; + // this._yAxis = hsl.lightness; + this._yAxis = (100 - hsl.saturation / 2) / 100 * hsl.lightness; + // console.log(this._hue, this._xAxis, this._yAxis) + this.emit('assigned', id); + this.emit('changed'); + } + + constructor() { + super(); + this.emit('changed'); + } +} + + +export function hslToRgbValues(h, s, l) { + h /= 360; + s /= 100; + l /= 100; + let r, g, b; + if (s === 0) { + r = g = b = l; // achromatic + } else { + const hue2rgb = (p, q, t) => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + const to255 = x => Math.round(x * 255); + r = to255(r); + g = to255(g); + b = to255(b); + return `${Math.round(r)},${Math.round(g)},${Math.round(b)}`; + // return `rgb(${r},${g},${b})`; +} +export function hslToHex(h, s, l) { + h /= 360; + s /= 100; + l /= 100; + let r, g, b; + if (s === 0) { + r = g = b = l; // achromatic + } else { + const hue2rgb = (p, q, t) => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + const toHex = x => { + const hex = Math.round(x * 255).toString(16); + return hex.length === 1 ? "0" + hex : hex; + }; + return `#${toHex(r)}${toHex(g)}${toHex(b)}`; +} + +// export function hexToHSL(hex) { +// // Remove the '#' if present +// hex = hex.replace(/^#/, ''); +// // Parse the hex value into RGB components +// const bigint = parseInt(hex, 16); +// const r = (bigint >> 16) & 255; +// const g = (bigint >> 8) & 255; +// const b = bigint & 255; +// // Normalize RGB values to range [0, 1] +// const normalizedR = r / 255; +// const normalizedG = g / 255; +// const normalizedB = b / 255; +// // Find the maximum and minimum values +// const max = Math.max(normalizedR, normalizedG, normalizedB); +// const min = Math.min(normalizedR, normalizedG, normalizedB); +// // Calculate the lightness +// const lightness = (max + min) / 2; +// // If the color is grayscale, set saturation to 0 +// if (max === min) { +// return { +// hue: 0, +// saturation: 0, +// lightness: lightness * 100 // Convert to percentage +// }; +// } +// // Calculate the saturation +// const d = max - min; +// const saturation = lightness > 0.5 ? d / (2 - max - min) : d / (max + min); +// // Calculate the hue +// let hue; +// if (max === normalizedR) { +// hue = ((normalizedG - normalizedB) / d + (normalizedG < normalizedB ? 6 : 0)) * 60; +// } else if (max === normalizedG) { +// hue = ((normalizedB - normalizedR) / d + 2) * 60; +// } else { +// hue = ((normalizedR - normalizedG) / d + 4) * 60; +// } +// return { +// hue: Math.round(hue), +// saturation: Math.round(saturation * 100), // Convert to percentage +// lightness: Math.round(lightness * 100) // Convert to percentage +// }; +// } + +export function hexToHSL(hex) { + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + + var r = parseInt(result[1], 16); + var g = parseInt(result[2], 16); + var b = parseInt(result[3], 16); + + r /= 255, g /= 255, b /= 255; + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, l = (max + min) / 2; + + if (max == min) { + h = s = 0; // achromatic + } else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + + s = s * 100; + s = Math.round(s); + l = l * 100; + l = Math.round(l); + h = Math.round(360 * h); + + return { + hue: h, + saturation: s, + lightness: l + }; +} diff --git a/.config/ags/widgets/sideleft/tools/colorpicker.js b/.config/ags/widgets/sideleft/tools/colorpicker.js new file mode 100644 index 000000000..016eeba6d --- /dev/null +++ b/.config/ags/widgets/sideleft/tools/colorpicker.js @@ -0,0 +1,284 @@ +// TODO: Make selection update when entry changes +const { Gtk } = imports.gi; +import App from 'resource:///com/github/Aylur/ags/app.js'; +import Variable from 'resource:///com/github/Aylur/ags/variable.js'; +import Widget from 'resource:///com/github/Aylur/ags/widget.js'; +import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; +const { execAsync, exec } = Utils; +const { Box, Button, Entry, EventBox, Icon, Label, Overlay, Scrollable } = Widget; +import SidebarModule from './module.js'; +import { MaterialIcon } from '../../../lib/materialicon.js'; +import { setupCursorHover } from '../../../lib/cursorhover.js'; + +import { ColorPickerSelection, hslToHex, hslToRgbValues, hexToHSL } from './color.js'; + +const clamp = (num, min, max) => Math.min(Math.max(num, min), max); + +export default () => { + const selectedColor = new ColorPickerSelection(); + function shouldUseBlackColor() { + return ((selectedColor.xAxis < 40 || (45 <= selectedColor.hue && selectedColor.hue <= 195)) && + selectedColor.yAxis > 60); + } + const colorBlack = 'rgba(0,0,0,0.9)'; + const colorWhite = 'rgba(255,255,255,0.9)'; + const hueRange = Box({ + homogeneous: true, + className: 'sidebar-module-colorpicker-wrapper', + children: [Box({ + className: 'sidebar-module-colorpicker-hue', + css: `background: linear-gradient(to bottom, #ff6666, #ffff66, #66dd66, #66ffff, #6666ff, #ff66ff, #ff6666);`, + })], + }); + const hueSlider = Box({ + vpack: 'start', + className: 'sidebar-module-colorpicker-cursorwrapper', + css: `margin-top: ${13.636 * selectedColor.hue / 360}rem;`, + homogeneous: true, + children: [Box({ + className: 'sidebar-module-colorpicker-hue-cursor', + })], + setup: (self) => self.hook(selectedColor, () => { + const widgetHeight = hueRange.children[0].get_allocated_height(); + self.setCss(`margin-top: ${13.636 * selectedColor.hue / 360}rem;`) + }), + }); + const hueSelector = Box({ + children: [EventBox({ + child: Overlay({ + child: hueRange, + overlays: [hueSlider], + }), + attribute: { + clicked: false, + setHue: (self, event) => { + const widgetHeight = hueRange.children[0].get_allocated_height(); + const [_, cursorX, cursorY] = event.get_coords(); + const cursorYPercent = clamp(cursorY / widgetHeight, 0, 1); + selectedColor.hue = Math.round(cursorYPercent * 360); + } + }, + setup: (self) => self + .on('motion-notify-event', (self, event) => { + if (!self.attribute.clicked) return; + self.attribute.setHue(self, event); + }) + .on('button-press-event', (self, event) => { + if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here + self.attribute.clicked = true; + self.attribute.setHue(self, event); + }) + .on('button-release-event', (self) => self.attribute.clicked = false) + , + })] + }); + const saturationAndLightnessRange = Box({ + homogeneous: true, + children: [Box({ + className: 'sidebar-module-colorpicker-saturationandlightness', + attribute: { + update: (self) => { + // css: `background: linear-gradient(to right, #ffffff, color);`, + self.setCss(`background: + linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,1)), + linear-gradient(to right, #ffffff, ${hslToHex(selectedColor.hue, 100, 50)}); + `); + }, + }, + setup: (self) => self + .hook(selectedColor, self.attribute.update, 'hue') + .hook(selectedColor, self.attribute.update, 'assigned') + , + })], + }); + const saturationAndLightnessCursor = Box({ + className: 'sidebar-module-colorpicker-saturationandlightness-cursorwrapper', + children: [Box({ + vpack: 'start', + hpack: 'start', + homogeneous: true, + css: ` + margin-left: ${13.636 * selectedColor.xAxis / 100}rem; + margin-top: ${13.636 * (100 - selectedColor.yAxis) / 100}rem; + `, // Why 13.636rem? see class name in stylesheet + attribute: { + update: (self) => { + const allocation = saturationAndLightnessRange.children[0].get_allocation(); + self.setCss(` + margin-left: ${13.636 * selectedColor.xAxis / 100}rem; + margin-top: ${13.636 * (100 - selectedColor.yAxis) / 100}rem; + `); // Why 13.636rem? see class name in stylesheet + } + }, + setup: (self) => self + .hook(selectedColor, self.attribute.update, 'sl') + .hook(selectedColor, self.attribute.update, 'assigned') + , + children: [Box({ + className: 'sidebar-module-colorpicker-saturationandlightness-cursor', + css: ` + background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}; + border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite}; + `, + attribute: { + update: (self) => { + self.setCss(` + background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}; + border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite}; + `); + } + }, + setup: (self) => self + .hook(selectedColor, self.attribute.update, 'sl') + .hook(selectedColor, self.attribute.update, 'hue') + .hook(selectedColor, self.attribute.update, 'assigned') + , + })], + })] + }); + const saturationAndLightnessSelector = Box({ + homogeneous: true, + className: 'sidebar-module-colorpicker-saturationandlightness-wrapper', + children: [EventBox({ + child: Overlay({ + child: saturationAndLightnessRange, + overlays: [saturationAndLightnessCursor], + }), + attribute: { + clicked: false, + setSaturationAndLightness: (self, event) => { + const allocation = saturationAndLightnessRange.children[0].get_allocation(); + const [_, cursorX, cursorY] = event.get_coords(); + const cursorXPercent = clamp(cursorX / allocation.width, 0, 1); + const cursorYPercent = clamp(cursorY / allocation.height, 0, 1); + selectedColor.xAxis = Math.round(cursorXPercent * 100); + selectedColor.yAxis = Math.round(100 - cursorYPercent * 100); + } + }, + setup: (self) => self + .on('motion-notify-event', (self, event) => { + if (!self.attribute.clicked) return; + self.attribute.setSaturationAndLightness(self, event); + }) + .on('button-press-event', (self, event) => { + if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here + self.attribute.clicked = true; + self.attribute.setSaturationAndLightness(self, event); + }) + .on('button-release-event', (self) => self.attribute.clicked = false) + , + })] + }); + const resultColorBox = Box({ + className: 'sidebar-module-colorpicker-result-box', + homogeneous: true, + css: `background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};`, + children: [Label({ + className: 'txt txt-small', + label: 'Result', + }),], + attribute: { + update: (self) => { + self.setCss(`background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};`); + self.children[0].setCss(`color: ${shouldUseBlackColor() ? colorBlack : colorWhite};`) + } + }, + setup: (self) => self + .hook(selectedColor, self.attribute.update, 'sl') + .hook(selectedColor, self.attribute.update, 'hue') + .hook(selectedColor, self.attribute.update, 'assigned') + , + }); + const ResultBox = ({ colorSystemName, updateCallback, copyCallback }) => Box({ + children: [ + Box({ + vertical: true, + hexpand: true, + children: [ + Label({ + xalign: 0, + className: 'txt-tiny', + label: colorSystemName, + }), + Overlay({ + child: Entry({ + widthChars: 10, + className: 'txt-small techfont', + attribute: { + id: 0, + update: updateCallback, + }, + setup: (self) => self + .hook(selectedColor, self.attribute.update, 'sl') + .hook(selectedColor, self.attribute.update, 'hue') + .hook(selectedColor, self.attribute.update, 'assigned') + // .on('activate', (self) => { + // const newColor = self.text; + // if (newColor.length != 7) return; + // selectedColor.setColorFromHex(self.text, self.attribute.id); + // }) + , + }), + }) + ] + }), + Button({ + child: MaterialIcon('content_copy', 'norm'), + onClicked: (self) => { + copyCallback(self); + self.child.label = 'done'; + Utils.timeout(1000, () => self.child.label = 'content_copy'); + }, + setup: setupCursorHover, + }) + ] + }); + const resultHex = ResultBox({ + colorSystemName: 'Hex', + updateCallback: (self, id) => { + if (id && self.attribute.id === id) return; + self.text = hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100)); + }, + copyCallback: () => Utils.execAsync(['wl-copy', `${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}`]), + }) + const resultRgb = ResultBox({ + colorSystemName: 'RGB', + updateCallback: (self, id) => { + if (id && self.attribute.id === id) return; + self.text = hslToRgbValues(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100)); + }, + copyCallback: () => Utils.execAsync(['wl-copy', `rgb(${hslToRgbValues(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))})`]), + }) + const resultHsl = ResultBox({ + colorSystemName: 'HSL', + updateCallback: (self, id) => { + if (id && self.attribute.id === id) return; + self.text = `${selectedColor.hue},${selectedColor.xAxis}%,${Math.round(selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}%`; + }, + copyCallback: () => Utils.execAsync(['wl-copy', `hsl(${selectedColor.hue},${selectedColor.xAxis}%,${Math.round(selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}%)`]), + }) + const result = Box({ + className: 'sidebar-module-colorpicker-result-area spacing-v-5 txt', + hexpand: true, + vertical: true, + children: [ + resultColorBox, + resultHex, + resultRgb, + resultHsl, + ] + }) + return SidebarModule({ + icon: MaterialIcon('colorize', 'norm'), + name: 'Color picker', + revealChild: false, + child: Box({ + className: 'spacing-h-5', + children: [ + hueSelector, + saturationAndLightnessSelector, + result, + ] + }) + }); +} \ No newline at end of file diff --git a/.config/ags/widgets/sideleft/tools/module.js b/.config/ags/widgets/sideleft/tools/module.js new file mode 100644 index 000000000..eae27147e --- /dev/null +++ b/.config/ags/widgets/sideleft/tools/module.js @@ -0,0 +1,56 @@ +import Widget from 'resource:///com/github/Aylur/ags/widget.js'; +import { setupCursorHover } from '../../../lib/cursorhover.js'; +import { MaterialIcon } from '../../../lib/materialicon.js'; +const { Box, Button, Icon, Label, Revealer } = Widget; + +export default ({ + icon, + name, + child, + revealChild = true, +}) => { + const headerButtonIcon = MaterialIcon(revealChild ? 'expand_less' : 'expand_more', 'norm'); + const header = Button({ + onClicked: () => { + content.revealChild = !content.revealChild; + headerButtonIcon.label = content.revealChild ? 'expand_less' : 'expand_more'; + }, + setup: setupCursorHover, + child: Box({ + className: 'txt spacing-h-10', + children: [ + icon, + Label({ + className: 'txt-norm', + label: `${name}`, + }), + Box({ + hexpand: true, + }), + Box({ + className: 'sidebar-module-btn-arrow', + homogeneous: true, + children: [headerButtonIcon], + }) + ] + }) + }); + const content = Revealer({ + revealChild: revealChild, + transition: 'slide_down', + transitionDuration: 200, + child: Box({ + className: 'margin-top-5', + homogeneous: true, + children: [child], + }), + }); + return Box({ + className: 'sidebar-module', + vertical: true, + children: [ + header, + content, + ] + }); +} \ No newline at end of file diff --git a/.config/ags/widgets/sideleft/tools/quickscripts.js b/.config/ags/widgets/sideleft/tools/quickscripts.js new file mode 100644 index 000000000..3ff907dc2 --- /dev/null +++ b/.config/ags/widgets/sideleft/tools/quickscripts.js @@ -0,0 +1,95 @@ +const { Gtk } = imports.gi; +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 { execAsync, exec } = Utils; +const { Box, Button, EventBox, Icon, Label, Scrollable } = Widget; +import SidebarModule from './module.js'; +import { MaterialIcon } from '../../../lib/materialicon.js'; +import { setupCursorHover } from '../../../lib/cursorhover.js'; + +Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets`); +const distroID = exec(`bash -c 'cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2'`).trim(); +const isDebianDistro = (distroID == 'linuxmint' || distroID == 'ubuntu' || distroID == 'debian' || distroID == 'zorin' || distroID == 'pop' || distroID == 'raspbian' || distroID == 'kali' || distroID == 'elementary'); +const isArchDistro = (distroID == 'arch' || distroID == 'endeavouros'); +const hasFlatpak = !!exec(`bash -c 'command -v flatpak'`); + +const scripts = [ + { + icon: 'nixos-symbolic', + name: 'Trim system generations to 5', + command: `sudo ${App.configDir}/scripts/quickscripts/nixos-trim-generations.sh 5 0 system`, + enabled: distroID == 'nixos', + }, + { + icon: 'nixos-symbolic', + name: 'Trim home manager generations to 5', + command: `${App.configDir}/scripts/quickscripts/nixos-trim-generations.sh 5 0 home-manager`, + enabled: distroID == 'nixos', + }, + { + icon: 'ubuntu-symbolic', + name: 'Update packages', + command: `sudo apt update && sudo apt upgrade -y`, + enabled: isDebianDistro, + }, + { + icon: 'fedora-symbolic', + name: 'Update packages', + command: `sudo dnf upgrade -y`, + enabled: distroID == 'fedora', + }, + { + icon: 'arch-symbolic', + name: 'Update packages', + command: `sudo pacman -Syyu`, + enabled: isArchDistro, + }, + { + icon: 'flatpak-symbolic', + name: 'Uninstall unused flatpak packages', + command: `flatpak uninstall --unused`, + enabled: hasFlatpak, + }, +]; + +export default () => SidebarModule({ + icon: MaterialIcon('code', 'norm'), + name: 'Quick scripts', + child: Box({ + vertical: true, + className: 'spacing-v-5', + children: scripts.map((script) => { + if (!script.enabled) return null; + const scriptStateIcon = MaterialIcon('not_started', 'norm'); + return Box({ + className: 'spacing-h-5 txt', + children: [ + Icon({ + className: 'sidebar-module-btn-icon txt-large', + icon: script.icon, + }), + Label({ + className: 'txt-small', + hpack: 'start', + hexpand: true, + label: script.name, + tooltipText: script.command, + }), + Button({ + className: 'sidebar-module-scripts-button', + child: scriptStateIcon, + onClicked: () => { + App.closeWindow('sideleft'); + execAsync([`bash`, `-c`, `foot fish -C "${script.command}"`]).catch(print) + .then(() => { + scriptStateIcon.label = 'done'; + }) + }, + setup: setupCursorHover, + }), + ], + }) + }), + }) +}); \ No newline at end of file diff --git a/.config/ags/widgets/sideright/notificationlist.js b/.config/ags/widgets/sideright/notificationlist.js index f3fa95111..f30802de4 100644 --- a/.config/ags/widgets/sideright/notificationlist.js +++ b/.config/ags/widgets/sideright/notificationlist.js @@ -20,7 +20,7 @@ export default (props) => { vertical: true, className: 'spacing-v-5', children: [ - MaterialIcon('notifications_active', 'badonkers'), + MaterialIcon('notifications_active', 'gigantic'), Label({ label: 'No notifications', className: 'txt-small' }), ] }), diff --git a/.config/ags/widgets/sideright/quicktoggles.js b/.config/ags/widgets/sideright/quicktoggles.js index 9ba354ddf..6d55f2611 100644 --- a/.config/ags/widgets/sideright/quicktoggles.js +++ b/.config/ags/widgets/sideright/quicktoggles.js @@ -1,4 +1,5 @@ const { GLib } = imports.gi; +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'; @@ -151,7 +152,7 @@ export const ModuleIdleInhibitor = (props = {}) => Widget.Button({ // TODO: Make self.toggleClassName('sidebar-button-active', self.attribute.enabled); if (self.attribute.enabled) { self.attribute.inhibitor = Utils.subprocess( - ['wayland-idle-inhibitor.py'], + [`${App.configDir}/scripts/wayland-idle-inhibitor.py`], (output) => print(output), (err) => logError(err), self, diff --git a/.config/ags/widgets/sideright/todolist.js b/.config/ags/widgets/sideright/todolist.js index 3efea6ad6..30a7941bf 100644 --- a/.config/ags/widgets/sideright/todolist.js +++ b/.config/ags/widgets/sideright/todolist.js @@ -92,7 +92,7 @@ const todoItems = (isDone) => Widget.Scrollable({ vpack: 'center', className: 'txt', children: [ - MaterialIcon(`${isDone ? 'checklist' : 'check_circle'}`, 'badonkers'), + MaterialIcon(`${isDone ? 'checklist' : 'check_circle'}`, 'gigantic'), Label({ label: `${isDone ? 'Finished tasks will go here' : 'Nothing here!'}` }) ] })