Author SHA1 Message Date
Gabriel Brown 08b16fa03f Fix live network and phone discovery 2026-08-18 12:34:02 -04:00
Gabriel Brown 60a321e6f4 Harden Health verification isolation 2026-08-18 12:15:45 -04:00
Gabriel Brown 7d5c65be03 Merge remote-tracking branch 'origin/main' into feat/panama-health
# Conflicts:
#	config/dot/quickshell/modules/settings/HealthPage.qml
#	tests/quickshell/health-ui-contract.sh
2026-08-18 11:41:33 -04:00
Gabriel Brown f8e7512e01 Document Panama health and recovery 2026-08-18 11:36:06 -04:00
Gabriel Brown b7ce2c6e43 Assert the installer's process isolation, not its formatting
panama-command-install-contract matched the literal string
`do "$script"; done`, so it failed the moment that loop gained error
reporting and spanned more than one line -- while the property it exists
to protect, each setup stage running in its own process, was unchanged.

It now checks that property directly: the installer must not source
anything under setup/scripts, and must execute them. Verified it still
catches an installer rewritten to source its stages, which the
first attempt at the replacement did not -- the pattern anchored to the
start of a line, and the sourcing appeared mid-line behind an `if`.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 11:35:52 -04:00
Gabriel Brown 1f62256024 Make a fresh install actually produce a working desktop
Two things stood between this repository and a machine that could
install it.

The installer aborted on its own first question. The hostname prompt
defaults to N, and the N branch ran `exit` -- so pressing Enter, the
obvious answer when you do not want to rename your machine, skipped the
entire installation and said nothing about it. Declining now just
declines. The installer is also safe to re-run, which is the upgrade
path too: it reports which stages failed instead of scrolling the
failure past twenty minutes ago, and restores the idle settings on every
exit path rather than only on success.

The package lists had drifted badly from what the configs and helpers
actually use. jq alone has thirty-one call sites across the helpers and
the contracts; kitty has a full shipped config and a dock pin; tmux and
btop have shipped themes the colour scheme switches; ddcutil, qrencode
and orca back features added today. None were declared. Neither were
fontconfig, pciutils, libselinux-utils, libnotify, wireplumber, fwupd or
python3-dnf, all of which shipped scripts invoke by name. A fresh
machine following this repository's own instructions would have got a
desktop whose features quietly were not there -- the helpers report "not
installed" rather than crashing, which is good behaviour and completely
silent.

So the lists are corrected and a contract now checks that every external
command Panama's scripts invoke is installed by Panama's packages.

Writing it was instructive about its own blind spots. The first version
reported `then`, `esac` and `done` as missing packages, burying the real
findings. The second passed while jq was undeclared, because the pattern
required three characters and jq is two -- a dependency checker with a
blind spot for short names is worse than none, since it reports PASS.
The third missed ddcutil, which is only ever invoked as `timeout 10
ddcutil` and so never appears statement-initial. It now also reads
`command -v X`, which is how these helpers probe for a tool and
therefore the clearest statement of a dependency there is. Verified it
catches jq, ddcutil and qrencode individually.

Also replaced a fixed 0.3s sleep in the write contract with a bounded
wait. It was failing about one run in three with "a rejected value did
not surface an error" when the error had simply not arrived yet, which
reads as a missing guard rather than a slow one.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 11:29:52 -04:00
Gabriel Brown 4ef2f01baa Merge remote-tracking branch 'origin/main' into feat/panama-health
# Conflicts:
#	config/dot/quickshell/modules/settings/ServicesPage.qml
#	config/dot/quickshell/modules/settings/SettingsShell.qml
#	config/dot/quickshell/modules/settings/SettingsSidebar.qml
2026-08-18 11:23:07 -04:00
Gabriel Brown 6042c0b1c0 Merge codex's System Health and recovery work
Brings in panama-doctor (a 25-check diagnostic with fixture-backed
contracts), a Health service, a System Health page replacing Startup &
Services, and a bar indicator that stays absent until something is
actually degraded. All seven of its contracts pass on the merge.

Three things needed resolving rather than accepting:

The branch predates the debranding, so its user-visible strings still
named the product -- "Panama desktop is healthy", "Restart Panama",
"Panama tools". Rewritten to say the same thing without the name, which
is what the rest of the app now does.

Its Fedora hand-off card was a single button calling openGnomePanel
("network") under a subtitle naming five subjects. Main had already
replaced that with a row per subject, each opening the panel that owns
it, so those rows are ported into HealthPage instead. Printers and
online accounts stay on Network & Devices with the rest of the network
hardware.

That broke its own assertion, which matched the literal
openGnomePanel("network") string. Rewritten rather than reverted: it now
checks the boundary card exists and that every panel named in HealthPage
is one openGnomePanel actually allows, since a name outside the
allow-list opens nothing at all. Verified it catches a plausible-looking
wrong name.

SettingsShell and SettingsSidebar conflicted because both sides added
pages; resolved as the union, keeping its System Health page and live
footer alongside main's Mouse & Touchpad, Privacy & Security, Region &
Language and Online Accounts.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 11:20:06 -04:00
Gabriel Brown 0c4d132ee1 Fix focus-mode labels that said the opposite of what they did
Found by codex's GNOME Tweaks audit and verified against the compositor:
`hyprctl descriptions` publishes input:follow_mouse as
map: [{"separate":3},{"detached":2},{"follow":1},{"disabled":0}].

Panama labelled 0 "Never", 1 "Click to focus", 2 "Sloppy focus". So this
desktop, sitting on the shipped value of 1, has been running
focus-follows-pointer the whole time while Settings called it "Click to
focus" -- and the way to actually GET click-to-focus was to choose
"Never". Value 3 was not offered at all. hypr/input.lua carried the same
wrong claim in a comment.

The shipped VALUE is left alone. Which focus mode this desktop should
use is a behaviour decision rather than a correction, and all four are
now reachable from Settings.

Nothing could have caught this. The compositor accepts 1, reads back 1,
and the write contract passes: the value is valid, it just means
something other than the label. The only authority on what each number
MEANS is the compositor, and it publishes that. So enum-hypr-map-contract
now checks every compositor-backed enum against the published map --
that offered values exist, and that published values are offered, since
a missing one is a capability nobody can reach.

Writing it immediately found two more of the same: variable refresh rate
offered Off and fullscreen-games while the compositor publishes four
(always-on and fullscreen-only were unreachable, and fullscreen-only is
what someone wanting VRR for video rather than games wants), and direct
scanout was missing its always-on value. Both now offer everything, with
a detail line per option rather than a bare word.

Verified the contract catches the original followMouse gap and a value
outside the map.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 11:13:42 -04:00
Gabriel Brown faa9a00716 Close Panama recovery race windows 2026-08-18 11:11:53 -04:00
Gabriel Brown 27af4fd443 Carry the colour scheme into btop and tmux
Two more applications that keep their own palette and so never followed
the desktop. btop was on "Default" and had never been themed at all;
tmux had Tokyo Night Moon hardcoded across seventeen lines, which meant
a dark status bar sitting under a light terminal in light mode.

Both themes are authored rather than borrowed. btop ships a
"tokyo-night" theme, but it is the Night variant (#1a1b26) where the
rest of this desktop is Moon (#222436), and two Tokyo Nights side by
side read as a mistake; it ships no Tokyo Night light theme at all.
Colours come from kitty's theme files so a terminal and what runs inside
it cannot disagree.

tmux follows kitty's shape: the colours move to themes/, tmux.conf
sources a generated current-theme.conf, and running servers are
re-sourced so an open session changes immediately rather than at next
launch. btop is different -- it OWNS btop.conf and rewrites it on exit,
so only the color_theme line is edited in place and the file is not
symlinked into the repository. btop reads its theme once at startup, so
a running instance keeps the old colours; forcing a restart would kill a
process the user is watching.

The tmux light theme took two passes. Mapping the palette role-for-role
put the standard Day accents on a mid-grey panel at 2.74:1 and 2.94:1 --
under the 3:1 floor, on a bar you read at a glance. It now uses Day's
darker accent variants on a lighter panel: 4.42:1 and 4.17:1, and
5.01:1 / 4.73:1 for the light text inside the inverted blocks.

The helper also now reports every target rather than only kitty. Saying
"kitty: applied" while silently skipping three other applications is
how a half-applied theme goes unnoticed.

Not changed: bat is configured with --theme ansi, which follows the
terminal's own palette, so it already tracks kitty with nothing to do.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 11:04:00 -04:00
Gabriel Brown b16834fea6 Version the settings file so it can be upgraded
The schema is the single source of truth for what a setting IS. It
cannot express what a setting USED to be -- and renaming a key, changing
its units, or splitting one setting into two all leave a stored value
the new schema does not recognise. Unrecognised keys are deliberately
carried through untouched so that rolling back to an older Panama does
not discard a newer version's settings, which means the user's choice
silently stops taking effect with nothing to explain it.

settings.json now carries a schemaVersion, and load() runs every pending
migration before anything reads a value. The list is empty: the point is
that the first breaking schema change becomes a routine edit rather than
an emergency, and Omarchy carries eighty of these.

The behaviours that make it safe to run against a real user's file:

  A file with no schemaVersion predates this and is STAMPED, not
  migrated -- running the list against it would apply upgrades designed
  for schemas it never had.

  A file from a NEWER Panama is left completely alone. Downgrading keys
  is not something this can do correctly, and unknown keys already
  survive, so an older build simply ignores what it does not understand.

  A step that throws stops at the last good version. Skipping past it
  would lose that conversion forever; failing the whole load would cost
  the user every setting.

The list being empty is exactly why this is tested now: the first time
it runs for real will be against somebody's actual settings during an
upgrade, which is a poor moment to find out how it behaves. The harness
supplies fixture steps including one that throws, and the contract pins
all four behaviours above plus the promise that unknown keys survive.

One thing the contract earned its place on: stamping a pre-versioning
file changes it without running any step, so writing only on "migrated"
left the stamp in memory to be redone on every launch. It now writes
whenever the version moves, and explicitly does not write a file from
the future.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 10:59:34 -04:00
Gabriel Brown 8bfae70284 Harden bounded Panama recovery actions 2026-08-18 10:58:48 -04:00
Gabriel Brown 4279da999a Share a Wi-Fi network by QR code
GNOME's Wi-Fi panel has this and it is the most-used thing in it: the
alternative is reading a passphrase out loud. Network & Devices now shows
a scannable code for any saved network whose passphrase this user can
read.

The image contains the network password in machine-readable form, so
most of the care here is about that rather than about QR codes. It is
written under XDG_RUNTIME_DIR -- 0700, on tmpfs, gone at logout --
rather than /tmp, which is shared; the file is 0600; the passphrase is
piped to qrencode on stdin rather than passed as an argument, because
argv is world-readable through /proc for as long as the process runs;
and it is never printed or included in an error message. Generated on
demand, because producing a code for every saved network up front means
writing images of passwords nobody asked to see.

Enterprise networks are listed as not shareable rather than offered and
broken: there is no passphrase to encode, so the code could not work.

Two bugs the contract caught while being written. Semicolons in an SSID
were not escaped -- the sed replacement had one backslash where its four
neighbours have two, so sed dropped it, and an SSID containing a
semicolon would have produced a QR code describing a different network.
And nmcli's trailing newline landed inside the payload; it decoded here,
but a newline in the middle of a WIFI: URI is not something every phone
tolerates, and that failure would present as "the QR code just doesn't
work on my phone".

The contract stubs nmcli and qrencode, because the real ones would write
this machine's actual Wi-Fi password into a fixture directory. It
asserts the escaping, the absence of a newline, the file and directory
modes, that no temporary payload survives, and that the passphrase never
reaches argv.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 10:56:01 -04:00
Gabriel Brown 54b8a82803 Give Accessibility a real magnifier, and stop implying the rest is available
The page had two settings and a hand-off card claiming GNOME's stack
provided "screen reader, zoom, and on-screen keyboard". Zoom did not
need handing off at all, and the claim about the rest was optimistic.

Zoom is native now. Hyprland has a real magnifier -- cursor:zoom_factor
follows the pointer -- so it is a slider here rather than a button that
opens another application. Inactive windows can also be dimmed as well
as faded, which is the other thing that makes a focused window
unmistakable. 47 mapped compositor options, from 43.

What is NOT here is the more useful half of the change. Sticky keys,
slow keys, bounce keys and mouse keys are AccessX, an X11 SERVER
feature. XKB under Wayland has no accessx option group at all -- checked
against evdev.lst, which lists altwin, caps, compose, ctrl, grp and the
rest, and nothing resembling accessx -- and Hyprland implements none of
it. The compositor will accept "accessx:enable" as a keyboard option and
store it happily; I verified that, and verified nothing acts on it.

So the page says plainly that they are unavailable in this session
rather than offering switches, and does not point at GNOME's panel for
them either: the daemon that would apply those keys is not running here,
so that hand-off would be just as empty. Orca is offered instead, since
the accessibility bus genuinely does work.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 10:49:04 -04:00
Gabriel Brown 2e8292599a Add power profiles to Power & Lock
The same Power Saver / Balanced / Performance choice GNOME's Power panel
offers, and the daemon behind it was already running here -- it simply
had no control anywhere in Panama. This machine has been sitting on
"performance" with nothing to say so.

Talks to the net.hadess.PowerProfiles interface rather than to a binary.
Fedora 44 implements it with tuned-ppd instead of power-profiles-daemon,
and powerprofilesctl is not installed at all, so anything shelling out
to that command would have found nothing while the service was right
there. Setting a profile needs no privileges: the daemon accepts a
property write from the active session user.

Not a stored preference. The daemon owns the profile, it survives Panama
restarts, and anything else on the system can change it, so a copy in
settings.json would drift -- the same reasoning as monitor brightness.

PerformanceDegraded is surfaced because it is what makes the setting a
lie: a thermally throttled machine reports "performance" while behaving
otherwise, and that is worth saying out loud.

The contract stubs busctl, because the real daemon is a system service
shared with everything else on the machine and a test that flipped the
daily driver to power-saver and then died would leave it there. It pins
the parsing in particular: busctl renders the Profiles property flat, so
profile names and driver names arrive in one stream, and a pattern loose
enough to match both reports the driver as an extra profile. On this
machine the driver is called "tuned", which reads exactly like a
plausible fourth profile -- verified the contract catches that, and
catches an unvalidated name reaching the system service.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 10:46:03 -04:00
Gabriel Brown 5562323eb8 Stop the live shell clobbering the write contract
This contract failed intermittently with "a typed batch did not reach
the compositor", reporting Panama's shipped defaults, and passed on
retry. The earlier guard against a lingering harness was not the cause --
no instance was alive.

Adding the writer's own error to the failure message settled it: the
writer reports NO error while the compositor holds defaults. A rejected
write leaves an error behind; a write that succeeded and was then
overwritten does not. Both this contract and the LIVE shell write to the
same compositor, and the running Panama re-applies its own preferences
on any store change -- landing exactly the values that were being
mistaken for "the write never happened".

So the apply is re-issued periodically while waiting, which makes the
test survive being overwritten without weakening what it asserts, and
the failure message now distinguishes the two cases instead of
describing both as a write that never arrived.

The wait is also 10 seconds rather than 4. The write path verifies every
option by reading it back and retries a refused batch, so a loaded
machine legitimately takes longer -- and this runs in a suite alongside
other tests driving the same compositor.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 10:39:42 -04:00
Gabriel Brown 2cc109f5e1 Add bounded Panama recovery actions 2026-08-18 10:35:18 -04:00
Gabriel Brown 52d896b054 Add an Online Accounts page
GNOME Online Accounts is a daemon plus a D-Bus API, and the daemon
already runs in this session -- gvfs activates it, and all four accounts
on this machine work without gnome-shell involved anywhere. Only the
PANEL was GNOME's. The accounts themselves are ordinary D-Bus objects
that anything may read and modify.

So everything except the initial sign-in is now native: the account
list, per-service toggles for mail, calendar, contacts, files, photos,
music and chat, and removal. That is the whole Online Accounts panel
apart from one OAuth handshake.

Signing in is the exception, and only for OAuth providers. The daemon's
AddAccount takes credentials as an argument -- it stores them, it does
not obtain them -- and the code that runs Google's OAuth exchange lives
in libgoa-backend, which Fedora ships without a GIR binding, so it is
reachable from C only. Reimplementing it would mean our own Google
client credentials. That step is handed to GNOME's panel and the page
says so, because a hand-off the user does not expect reads as a bug.
Password-based providers (Nextcloud, IMAP, WebDAV) could be added
natively later; their credential keys are known now.

Accounts needing re-authentication are surfaced first, which turned up
something immediately: both Google accounts on this machine report
attention_needed, meaning their tokens have expired and they have
stopped syncing. GOA has known that all along and nothing outside its
own panel ever said so.

Every write re-reads the account list rather than assuming it landed.
GOA can refuse, and a toggle that springs back is the honest outcome.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 10:34:41 -04:00
Gabriel Brown e4409ed6aa Surface the login keyring, and offer to unlock it
The keyring is already unlocked at sign-in exactly as GNOME does it --
pam_gnome_keyring is in GDM's stack and the journal confirms it works
("gnome-keyring-daemon started properly and unlocked keyring"). So there
was no configuration bug to fix. What a bare Hyprland session lacks is
anywhere to see when that has stopped being true.

It stops being true rarely and expensively. gnome-keyring-daemon crashed
once on this machine -- an upstream abort in service_method_open_session,
with a core dump -- and D-Bus then activated a replacement. That
replacement never received the login password, so the keyring was locked
in the middle of a session that had unlocked it correctly at login.
Nothing announces this. What you see instead is a mail account that will
not authenticate, a git push that cannot find its key, or an integration
reporting "not configured", none of which mention keyrings. That is the
same root cause as the Home Assistant token failure earlier.

Privacy & Security now shows the state, offers an Unlock action that
raises the standard password dialog, and reports when the daemon holding
your secrets is a D-Bus replacement rather than PAM's -- because a
replacement that is currently unlocked was unlocked by hand and will not
survive a restart. The password never passes through Panama.

The contract stubs the secret service rather than touching the real one:
locking the login keyring breaks every saved password on the machine and
can only be undone by typing the password into a dialog, so it is not
something a test suite may do to a daily driver. Verified it catches a
helper that misreports locked as unlocked, and one that crashes instead
of reporting a missing service.

Worth recording: a locked keyring makes a NON-INTERACTIVE caller appear
to hang. It is not hung -- it is waiting on a dialog nobody is looking
at, which is exactly how the earlier secret-tool investigation lost an
hour.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 10:34:41 -04:00
Gabriel Brown f0321432b4 Fix dark mode: the GTK theme it asked for does not exist
Reported symptom: Electron applications and a Chromium-based browser,
all set to follow the system, went light when the desktop went light and
never came back. Nothing reported an error, and the portal was serving
the correct value the whole time.

Cause: ColorScheme set gtk-theme to "Adwaita-dark" for dark and
"Adwaita" for light. Neither is installed on Fedora 44 -- only adw-gtk3
and adw-gtk3-dark are. GTK responds to an unknown theme name by falling
back to its built-in default, which is LIGHT. So asking for light worked
by accident, asking for dark silently produced light, and anything that
takes its cue from the GTK theme rather than the portal stayed light no
matter what org.freedesktop.appearance said. Verified: the portal emits
correctly in both directions, so this was never the portal's fault.

Second cause, the mirror of the first: gtk-3.0/settings.ini and
gtk-4.0/settings.ini were pinned to adw-gtk3-dark and prefer-dark=1 and
never regenerated. Under GNOME that file is ignored because
gnome-settings-daemon publishes over XSETTINGS; under Hyprland nothing
does, so for GTK3 it is authoritative -- and it contradicted the scheme
in light mode. They are now generated from a template on every switch,
gitignored as machine state, and seeded by link-dotfiles, the same shape
kitty's current-theme.conf already uses. These directories are symlinked
into the repository, so writing the live file directly would dirty the
working tree on every theme switch.

The failure was silent by construction, so it gets a test rather than a
comment: gtk-theme-contract asserts every theme name Panama sets is
actually installed, and that the generated GTK config agrees with the
scheme in both directions. Verified it catches both original bugs.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 10:34:19 -04:00
Gabriel Brown d855a26bd9 Add quiet System Health entry points 2026-08-18 10:19:03 -04:00
Gabriel Brown 607acb0a2d Make About answer what fastfetch answers
About reported four component versions and, since this morning, a short
hardware summary. It now covers what someone actually wants from an
About page or a fastfetch run: operating system, model, hostname,
kernel, uptime, package counts, shell, locale, windowing system,
resolution, processor, graphics, memory, swap, and disk.

Rows are ordered the way fastfetch presents them -- what the system is,
then what is installed on it, then the hardware underneath -- and the
GPU rows are spliced in directly after Processor rather than appended,
because a graphics card listed after "Disk" reads as an afterthought.
Graphics is still joined from GraphicsDevices rather than read a second
time, so the two readouts cannot disagree.

Memory is total, not used. About is not a monitor: a "12.4 GiB used"
figure is stale before it finishes drawing, and the Home page's vitals
readout is where live numbers belong. Disk is the exception because free
space does not move while you look at it.

Package counting is why this whole helper runs on demand -- rpm -qa on a
full workstation is a few thousand lines and takes a moment. Swap and
package counts are omitted entirely when they are zero or the tools are
absent, rather than reported as "0" or "Unknown".

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 10:09:06 -04:00
Gabriel Brown 9b2fa80ab7 Drop the product branding: this is just Settings
It is the system settings app for this desktop, so it is named the way
one is. The window is "Settings", the titlebar is "Settings", the
wordmark above the search field is gone, the sidebar's last row is
"About", and the status line reads "Desktop is healthy".

Prose followed the same rule. Forty-odd strings explained what "Panama"
does -- "Panama never animates while idle", "Restore Panama defaults",
"Panama looks in ~/Pictures/Wallpapers" -- which is how a product
describes itself, not how a settings panel describes a setting. They now
say what happens. No user-visible "Panama" remains anywhere in the app.

Two consequences worth naming:

The SUPER+I shortcut's description is user-visible, because the
Shortcuts page is generated from it, so that is renamed too. Rebindings
are keyed by shipped chord rather than description, so no existing
override is orphaned by this.

Three contracts matched the window by title and one matched that
shortcut by description; all four are updated. There are no Hyprland
window rules keyed on the title, so nothing about the window's placement
changes.

The desktop entry is now Name=Settings, but the FILE keeps its
panama-settings name, as does the icon: the dock pins applications by
desktop id, and renaming the file would silently unpin it.
dock-pins-contract covers exactly that.

The dated design docs under docs/superpowers keep the old name. They are
a record of what was decided when, and editing them to agree with the
present would make them lie about the past.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 10:08:55 -04:00
Gabriel Brown 8a0a09dbb4 Fix System Health ledger rendering 2026-08-18 10:08:12 -04:00
Gabriel Brown 816ea68f9a Read DDC/CI from the AUX bus, not the EDID bus
With I2C access granted, the helper still found nothing: every connected
monitor was probed on the wrong bus.

A connector has two. The `ddc` symlink points at the classic I2C line
that carries EDID on HDMI and DVI. DisplayPort carries DDC/CI over the
AUX channel instead, which appears as a child directory of the
connector. Both exist on a DP connector and both resolve, so the wrong
choice looks entirely reasonable and simply finds no monitor: this
machine's DP-2 has ddc -> i2c-5, where ddcutil reports "No monitor
detected", while its AUX child i2c-9 answers VCP 0x10 immediately.

This is the guess the previous commit said was unverified, and it was
wrong in the way that mattered. Enumerating from sysfs is still right --
it gives the connector name Hyprland uses and skips empty connectors --
but it has to prefer the AUX child and fall back to the symlink.

The fixture now mirrors sysfs properly: /sys/class/drm/<connector> is a
SYMLINK to the real device directory, and `find` does not follow the
path it is given. A fixture built from plain directories passes whether
or not the code resolves the symlink first, which is a test that agrees
with itself rather than with the kernel.

Verified on hardware: the Kuycon P20 reports 100%, accepts 70 and
returns to 100, and the media keys move it through codex's OSD path.
Both bus-selection mistakes are now caught by the contract.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 09:51:55 -04:00
Gabriel Brown 3238623934 Build the System Health settings page 2026-08-18 09:50:15 -04:00
Gabriel Brown 21aa9223df Harden Panama health snapshots 2026-08-18 09:30:18 -04:00
Gabriel Brown e2e03252e4 Add Panama health state service 2026-08-18 09:20:16 -04:00
Gabriel Brown 0ca83f74c7 Harden Panama doctor probes 2026-08-18 09:00:35 -04:00
Gabriel Brown d58c431199 Add Panama system health diagnostics 2026-08-18 08:52:49 -04:00
Gabriel Brown 3ff414fb1b Stop two contracts from driving each other's shell
settings-hyprland-write-contract failed in full suite runs and passed on
its own, reporting "a typed batch did not reach the compositor" with
every value at its default.

Both it and settings-commit-reset-contract drive the same harness file,
and Quickshell identifies an instance by its config path -- not by the
environment it was launched with. So when one run's instance has not
fully exited, the other's wait for `ipc show` is satisfied by that
instance's target, and the whole contract then talks to a shell it did
not start.

The two directions fail differently, and the second is the alarming one:

  The write contract lands on the isolated instance, whose compositor
  write seam is deliberately stubbed. Its writes go nowhere, which is
  exactly the symptom above.

  The commit/reset contract lands on the non-isolated instance and
  drives the DAILY DESKTOP's real compositor while believing it is
  isolated.

Both now refuse to start while another instance of that harness is
alive, and say which hazard they are avoiding rather than failing on an
assertion much later.

One trap worth naming, since it bit me writing this: `rg -c` prints
nothing at all when there are no matches, so an unguarded command
substitution yields "" and not "0" -- the first version of the guard
fired on a perfectly clean machine.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 08:43:59 -04:00
Gabriel Brown 77f8102148 Approve diagnostic ledger health design 2026-08-18 08:39:47 -04:00
Gabriel Brown 0fd59c59e3 Answer GNOME's Search panel, and pin the settings navigation wiring
GNOME's Search panel configures which applications provide results in
gnome-shell's overview and which folders are indexed. gnome-shell does
not run here, so reimplementing those switches would store preferences
that change nothing. Under Panama searching is the launcher's job and
Vicinae carries its own preferences, so the Applications page says where
search lives and how to rebind the keys that open it, rather than
offering settings that would be inert or duplicated.

The nav contract is the more useful half. Adding a settings page means
editing four files, and missing any one of them fails quietly in a
different way: no sidebar row, a row that silently shows Home, a deep
link that redirects to Home, or -- worst -- a missing qmldir entry,
which makes the page "not a type" and takes the entire settings window
down with it. Nothing at runtime cross-checks the four. Having just
added four pages by hand, this checks them statically, and also fails on
a page file that exists but is unreachable.

Verified it catches a missing qmldir registration and a missing
allow-list entry.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 08:38:54 -04:00
Gabriel Brown b1f9891849 Add multitasking controls, and a light theme for the launcher
Two things, both closing gaps in work that was already reported done.

GNOME's Multitasking panel, in Hyprland's terms: tiling layout, split
behaviour, floating-window snapping, workspace wrap-around and
back-and-forth, whether applications may take focus, and whether the
pointer changes the active display. Hyprland creates and destroys
workspaces as you use them, so there is no fixed count to expose, and
the page says so rather than leaving a conspicuous absence.

The Desktop page described the first two of these as read-only facts --
"Layout: Tiling", "Workspace movement: Dynamic" -- which was never true.
Both are ordinary Hyprland options that happened to have no controls,
and TextRow's own documentation says a setting the user could reasonably
change does not belong in it. Schema defaults are Panama's shipped
values from looks.lua rather than Hyprland's own, so restoring defaults
returns the desktop to how it ships. 43 mapped options now, from 35.

The launcher had no light theme. vicinae.json already selected a theme
per system appearance, but both entries pointed at Moon, so choosing
light mode left the most frequently opened window on the desktop dark --
a hole in the light/dark work, not a missing feature. Day is authored
from the same palette as the kitty Day theme so the two cannot drift,
and link-dotfiles now installs every authored theme rather than only the
dark one, which is why the gap survived being noticed.

Its placeholder colour is not Tokyo Night Day's own: that measures
2.54:1 against the background, below the 3:1 floor for secondary text.
This is 3.25:1, the same value used for neovim's light comments.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 08:34:05 -04:00
Gabriel Brown 6026bf308b Add Region & Language, and answer "what am I running on" in About
More GNOME Settings parity.

Region & Language is new. The locale is localectl's, and Panama stores no
copy of it -- there is exactly one system locale, so a preference here
would be a second source of truth that drifts the moment anything else
changes it. Codes are resolved against iso-codes into "Portuguese
(Brazil)" the way GNOME does, with the code kept visible because it is
what actually gets written and someone choosing between two Spanish
variants needs to see it. Changing it is privileged and only applies to
programs started afterwards, so the page says a sign-out is needed
rather than claiming the new language is in use.

The service is called SystemLocale, not Locale: QML has a built-in
Locale value type that silently shadows a singleton of that name, and
every binding then reads properties off the wrong thing. The page
rendered empty with nothing but "cannot read property of undefined" to
explain it.

About now answers what GNOME's About answers -- model, processor,
memory, disk, OS, kernel, windowing system -- where before it listed
only Panama's own component versions. Graphics is joined from
GraphicsDevices rather than read again, because two readouts of the same
hardware are two things that can disagree. Placeholder DMI strings
("To Be Filled By O.E.M.") are filtered out, and unreadable facts are
omitted rather than shown as "Unknown".

The Fedora hand-off card was one row listing five subjects that opened
the network panel regardless. Naming a panel and then not opening it
reads as a broken button rather than a deliberate hand-off. Each subject
now opens the panel that owns it, and openGnomePanel takes an optional
subpage so "Users" reaches System's users page the way GNOME's own
desktop entry does, instead of dropping the user on System's front page.
Printers and online accounts are not repeated here; they stay with the
network hardware on Network & Devices.

One bug this surfaced, caught by the hyprland write contract: the Lua
config key and the hyprctl option name genuinely differ for tap to
click. hl.config wants input.touchpad.tap_to_click; getoption answers to
input:touchpad:tap-to-click. Either spelling used for both fails -- a
hyphen is not a Lua identifier, and the underscored name is not a known
option -- which is what the schema's two separate fields are for.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 08:30:21 -04:00
Gabriel Brown 24d8bfd641 Plan Panama health and recovery 2026-08-18 08:28:20 -04:00
Gabriel Brown 14529c011f Add a Privacy & Security page
Continuing towards GNOME Settings parity. GNOME's Privacy panel covers
screen lock, camera and microphone access, file history, trash, and
device security; Panama had no equivalent page at all, despite already
tracking camera and microphone use for the bar indicator.

Device security is a new read-only readout: Secure Boot, TPM, disk
encryption, SELinux mode, and the firewall. None of these is a
preference -- they are set in firmware, at install time, or by system
policy, and a switch offering to change them would either fail or do
something far-reaching from a control that looks like every other
control. What it answers is "is this machine set up the way I think it
is", which otherwise takes five commands and root. Facts that cannot be
determined report Unknown rather than guessing, because a security
readout that quietly says "fine" when it failed to look is worse than
no readout.

File history and trash retention are deliberately NOT offered as
switches. They are GNOME preferences enforced by gsd-housekeeping, which
does not run in a Hyprland session -- verified, it is not running here.
Toggling them would store a preference, change nothing, and give no sign
of it. They are delegated to GNOME Settings by name instead.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 08:20:04 -04:00
Gabriel Brown 7378c18ab9 Merge remote-tracking branch 'origin/fix/osd-ddc-brightness' 2026-08-18 08:16:49 -04:00
Gabriel Brown 9ce7040b91 Add a Mouse & Touchpad page and make keyboard layout editable
Working towards parity with GNOME Settings, which splits pointing
devices into their own panel. Panama had pointer speed and focus-follows
buried under a page called "Input & Shortcuts", and had nothing at all
for scroll direction, acceleration profile, scroll speed, left-handed
buttons, or any touchpad setting -- all of which could only be changed
by editing hypr/input.lua by hand, which is the thing this app exists to
stop.

Every new mapping was read back off the running compositor rather than
assumed, and two were not what they look like: touchpad drag lock is an
int with three states, not a switch, and scroll factors are floats even
at their default of exactly 1. Getting either wrong makes every write to
that setting look rejected. The shape contract now covers 35 mapped
options, up from 23.

The touchpad card renders only when a touchpad is attached, which is
what InputDevices is for. On a desktop it would be worse than useless:
every switch on it would appear to work, because the preference is
stored and Hyprland accepts an option for a device class it has no
member of, so the settings would silently affect nothing.

Keyboard layout was read-only text, justified by a note saying changes
needed a compositor reload. That is not true in 0.56.2 -- setting
input:kb_variant through hl.config re-keymaps attached keyboards
immediately, verified by watching active_keymap on a real keyboard
change to "English (US, intl., with dead keys)" and back. So layout,
variant, and options are now real controls, joined by a TextEntryRow
that commits on Enter or focus loss rather than per keystroke, since
half a layout name is a valid string meaning something else.

Rejected input is shown as rejected rather than sanitised: these strings
are serialised into an hl.config payload, where stripping an unexpected
character would turn a typo into a different working setting.

Verified each new pointer option applies and reverts against the live
compositor. Schema, search, commit/reset, and system contracts pass.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 08:16:10 -04:00
Gabriel Brown af0ba13573 Design Panama Health and Recovery 2026-08-18 08:15:51 -04:00
Gabriel Brown 09e5f1ad6b Fix external monitor brightness OSD 2026-08-18 08:15:01 -04:00
Gabriel Brown 7541a45e77 Add external monitor brightness over DDC/CI
brightnessctl drives the kernel backlight class, which laptop panels
have and this desktop does not -- it reports only keyboard and NIC LEDs.
So BrightnessControl removed itself and there was no way to dim the
screen from Panama at all. DDC/CI is the channel the buttons on a
monitor's bezel drive, and it is the only brightness an external display
has. Both sources now render a row each, so a machine gets whichever it
actually has, or none.

Displays are enumerated from sysfs rather than `ddcutil detect`. The
kernel publishes the connector-to-bus mapping as
/sys/class/drm/<card>-<connector>/ddc along with whether anything is
plugged in, which beats parsing detect's undocumented brief output,
yields the connector name spelled exactly as Hyprland spells it, and
probes only connectors with a monitor attached -- one bus on this
machine rather than fourteen, where each empty bus costs a timeout.
No model name is read: Hyprland already knows what every output is
called, so the UI joins on the connector instead of keeping a second
source of truth that could disagree with the Displays page.

Writes are debounced, serial, and read back. Serial because DDC/CI has
no arbitration and two ddcutil processes on one bus interleave their
exchanges and both return garbage. Read back because a write is not a
promise: panels clamp to their own range, ignore values while waking
from standby, and drop writes that arrive too fast. Without the read the
slider would show what Panama asked for rather than what the monitor
did, which is the same class of lie as trusting `hyprctl keyword`.

Brightness is deliberately not a stored preference. The monitor
remembers it and the bezel buttons change it behind Panama's back, so
persisting it would mean restoring a value the panel had moved past.

The contract runs against fixtures with ddcutil stubbed and both sysfs
roots redirected, so it never touches a real monitor. Its fixture
reports a maximum of 200 rather than 100 on purpose -- at 100 the
scaling arithmetic is the identity and a helper that ignored the
reported maximum would pass everything. Verified it catches that, plus a
dropped connection-status filter and an unstripped connector prefix.

Not yet confirmed against hardware: this machine cannot open any I2C bus
yet. ddcutil's udev rule grants that through uaccess but only to devices
created after it was installed, so it needs one udevadm trigger. The
helper detects exactly that case and returns the command as its error
rather than reporting "no displays".

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 08:01:49 -04:00
Gabriel Brown 1a91d00f2c Stop two contracts from latching a broken desktop state
Both tests run against the live session and capture "what it was before"
so they can put it back. Neither checked that what they found was sane,
so one interrupted run poisoned every run after it -- and because each
subsequent run faithfully restored the bad value, the desktop stayed
broken while the failure looked like an ordinary flake.

displays-contract left the monitor at scale 1.25 after a failed revert.
The next run recorded 1.25 as the original and restored the desktop to
it. It now reads the shipped scale out of monitors.lua and refuses to
run when the live display disagrees. A failure to parse that value is
fatal rather than skipped, because silently skipping the check is how
the laundering happened in the first place.

focus-session-expiry kills and restarts the shell mid-session, so an
interrupted run leaves caffeine on with nothing left to turn it off. The
next `focus start` recorded "previously on", handed it back on expiry,
and failed the assertion that caffeine ends off -- identically, forever,
with the desktop unable to idle or lock the entire time. It now refuses
to start unless caffeine is already off, which is the only state in
which the test can tell "restored correctly" from "never released".

Both guards name the exact command to recover with. Verified each fires
on a dirty state and passes on a clean one; caffeine was found latched
on this machine and has been released.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 07:48:02 -04:00
Gabriel Brown 19063a3e02 Carry the colour scheme into terminals and the editor
The scheme switch already reached everything that reads
org.freedesktop.appearance -- GTK4, Qt6, Chromium, Electron -- because
ColorScheme.qml writes the gsettings key those all watch. Applications
carrying their own palettes did not follow, so choosing light mode left
the two windows actually used all day, kitty and neovim, still dark.

kitty: the 32 colours move out of kitty.conf into themes/, and kitty.conf
ends with `include current-theme.conf`. The generated file is machine
state rather than configuration, so it is gitignored and link-dotfiles
seeds it on install -- otherwise a fresh checkout starts by complaining
about a missing include. Running terminals are re-coloured in place over
their control sockets; a restart is not needed.

neovim: reads settings.json directly, since it neither watches the portal
nor keeps a socket open. Tokyo Night ships Day in the same family as
Moon, so light mode keeps the editor's identity instead of turning it
into a different-looking application. The existing readability overrides
were written against Moon and are now dark-only -- applied to Day they
would have put light grey on a light background, the same problem they
exist to fix, inverted. Light mode gets one override of its own:
tokyonight's shipped comment colour measures 2.54:1 against Day's
background, under the 3:1 floor for secondary text, so it is replaced
with 3.25:1 -- readable, still dimmer than Normal's 4.52:1.

An editor already open when the scheme flips re-applies on FocusGained,
which is cheap and fires exactly when the mismatch would be noticed.

Verified both directions: kitty re-coloured 4 live terminals, and neovim
starts as tokyonight-day with background=light and tokyonight-moon with
background=dark.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 07:47:51 -04:00
Gabriel Brown 5e6dc9e533 Release stale Caffeine inhibitors 2026-08-18 07:43:15 -04:00
Gabriel Brown ca64d4b9b6 Harden Panama launcher actions 2026-08-18 07:31:11 -04:00
Gabriel Brown b28edcd01f Add Panama commands to the launcher 2026-08-18 07:31:11 -04:00
Gabriel Brown 9c574f9eb7 Add a light mode that keeps the same identity
Light is Tokyo Night Day, the palette's own light variant, rather than
one invented to merely not be dark. Both share the same hues at
different lightness, which is what lets the Prism signature survive the
switch: blue still leads into orchid, it is simply a darker blue on a
lighter ground.

Every colour token became a binding on one boolean, so flipping it
repaints the whole shell without any component needing to know it
happened. That only worked because nothing outside Theme.qml defines a
colour; the two places that did are fixed here.

Surface alphas differ by scheme. The 0.34 that reads as glass over a
dark desktop reads as haze over a light one, and text stops being
legible on it.

Two things that draw on this desktop do not read Panama's store: GTK
applications, which read gsettings, and the compositor, which draws
window borders. A toolbar or a border still wearing the other scheme is
more jarring than either scheme on its own, so ColorScheme pushes the
choice to both. It also pushes at startup, since a scheme chosen in a
previous session would otherwise be in effect only for the shell.

The unfocused window border follows too. It is a flat neutral, and a
dark neutral is invisible against a light desktop. The focused border is
the prism gradient and needs no variant.

The live preview follows the scheme as well. A preview that stayed dark
while the shell around it went light did not read as "your desktop", it
read as a screenshot of someone else's.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 07:20:35 -04:00
Gabriel Brown d1b2cd2083 Make typography choosable instead of hardcoded
Theme.qml was the largest remaining thing in this desktop that could
only be changed by editing a file, and typeface is the first thing
someone changes when they want a desktop to feel like theirs. Interface
font, icon font, and the base text size are settings now.

The two font choices are deliberately separate lists. Theme.fontMono is
used only to draw glyphs -- workspace pills, the status cluster, search
icons -- so a plain monospace family there replaces every icon in the
shell with tofu. The picker offers only Nerd Fonts for that slot and
says why.

Candidates are rendered in the family they name. A list of font names
set in the current font tells you nothing about what you are choosing.

The four type sizes derive from the base rather than being stored
separately, so the relationship between body, caption, heading and title
survives a change instead of four numbers drifting apart.

hypr/looks.lua reads the same key. Following the preference only on the
QML side would leave the compositor and the shell disagreeing about the
interface font, which nobody notices until a tooltip renders in a
different typeface.

Only a family this machine reports is accepted: the value reaches
hl.config as a string, and a settings file moved between machines will
name fonts that are not installed. A missing family is reported rather
than silently substituted by fontconfig.

Also collapses the wallpaper grid to two rows. Sixty tiles is two
screens of pictures on a page that also holds typography, window
geometry and effects -- everything below it was unreachable without
scrolling past all of them.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 07:06:53 -04:00
Gabriel Brown 8c4dee0ca8 Give Settings its own icon and the first dock slot
Panama Settings shipped with Icon=preferences-system-symbolic, a
monochrome glyph drawn for 16px toolbar use. Beside full-colour
application icons in a 48px dock it reads as a missing icon rather than
a quiet one. It now has its own: a gear, because a settings icon has to
be recognisable before it is clever, rendered in the Prism gradient on
the dark tile so it belongs to this desktop.

An earlier attempt drew the gear as a ring with radial strokes; at dock
size the strokes merged into the ring and it read as an X. The shipped
version is a real toothed outline, checked at 48px rather than only at
128.

The dock also pinned GNOME Settings first. Panama now covers what GNOME
Settings did for this desktop and delegates the remainder to it by name,
so pinning the thing it delegates TO put the fallback in front of the
real one. GNOME Settings stays installed and searchable.

link-dotfiles installs icons alongside desktop entries, so this survives
a fresh setup rather than being a file that happens to exist here.

The new contract asserts every pinned application resolves to an
installed desktop entry. DockBody drops an unresolvable pin rather than
drawing a broken icon, which is right at runtime and invisible to debug:
a typo or a renamed desktop id just removes an icon with nothing logged.
It also cost me a false negative while writing it -- DesktopEntries
populates asynchronously, and asking too early reports every pin as
missing.

Also makes tests/quickshell/osd-ui-contract.sh executable. It was
committed mode 644, the only test in the suite that was, so the runner
could not invoke it. It passes.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 06:54:43 -04:00
Gabriel Brown 71ee18048e Add a living Prism component gallery 2026-08-18 06:50:32 -04:00
Gabriel Brown 668983e16d Add polished system feedback overlays 2026-08-18 06:46:11 -04:00
Gabriel Brown 17e23a6cf4 Merge remaining settings work
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 06:38:46 -04:00
Gabriel Brown d18fe51553 Make the weather location and graphics device choosable
The last two values that could only be changed by editing a file.

Weather was pinned to hardcoded coordinates, so the card could not be
pointed anywhere else. It is a location search now, not latitude and
longitude fields: nobody knows their own coordinates, and a control that
demands them is one nobody uses. Open-Meteo's geocoding endpoint needs
no key, the same reason the forecast already uses them. Only the search
term leaves the machine -- the stored place name is a label -- and
coordinates are rounded to four decimals, far finer than a weather
reading resolves and coarse enough to keep a precise home location out
of the settings file.

The graphics readout was hardcoded to card1. This machine has two amdgpu
cards, discrete and integrated, so that was right only by luck, and the
path is meaningless on any other machine. GPUs are enumerated with a
readable name from lspci, since sysfs exposes only numeric ids, and the
picker appears only when there is more than one to choose between. A
stored path the machine does not have is refused and reported rather
than silently measuring nothing.

Also merges the per-application notification rules UI. Its three commits
were believed integrated but the page half was not actually in the tree:
main had the service side in Notifs.qml and zero references to
setAppRule in NotificationsPage. Ancestry is not content.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 06:38:46 -04:00
Gabriel Brown 9f9515ffe0 Merge per-application notification rules
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 06:29:47 -04:00
Gabriel Brown 94353aa0bd Close the gaps the cross-UI audit found
Audited bar, dock, quick settings, date menu and Settings for three
things: a setting reachable in one UI but not another, a setting that
exists but is unreachable anywhere, and UI that states something false.

Night Light was fully exposed in Quick Settings and had no control
anywhere in Settings. It now has a card on Displays, where GNOME also
puts it, with on/off, schedule, times and temperature.

Adding those controls would have shipped the exact defect this audit
exists to find. NightLight declared enabled, temperature and automatic
as bindings on the store, but toggle() assigns to them, and an
assignment destroys a QML binding permanently -- so the service wrote to
the store and never read from it again. The Settings controls would have
written values the service ignored, while Quick Settings kept working.
It now follows the store. Every other service was swept for the same
pattern; this was the only one.

The night light schedule was two hardcoded literals, so the hours could
not be changed. They are schema keys now, with a row that renders 17.5
as "5:30 PM" and honours the 24-hour preference rather than showing a
decimal nobody reads as a time.

keyboardLayout was in the schema and read by input.lua but had no
control anywhere: configurable in principle, unreachable in practice. It
is surfaced on Input & Shortcuts as read-only, with the reason, because
it needs a compositor reload and a control implying instant apply would
be a smaller lie but still a lie.

Caffeine was a Quick Settings toggle mentioned only in a subtitle in
Settings. It has a real control now.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 06:25:15 -04:00
Gabriel Brown 3a2295ff53 Make credential setup keyboard-safe and verifiable 2026-08-18 06:14:07 -04:00
Gabriel Brown de1b8a7673 Add private Home Assistant credentials settings 2026-08-18 06:14:07 -04:00
Gabriel Brown 39a26adcc2 Make Home light ordering explicit and durable 2026-08-18 06:14:07 -04:00
Gabriel Brown eff0bc44ff fix: resolve persisted notification app labels 2026-08-18 06:14:07 -04:00
Gabriel Brown c038c57278 fix: harden notification application rule integration 2026-08-18 06:14:07 -04:00
Gabriel Brown 420b7aa1a6 feat: add notification application rules 2026-08-18 06:14:07 -04:00
Gabriel Brown f22e405b50 fix: resolve persisted notification app labels 2026-08-18 05:57:10 -04:00
Gabriel Brown 3cfa592db9 Build complete PipeWire sound settings 2026-08-18 05:54:14 -04:00
Gabriel Brown 81096e2d95 Declare the Sound and notification-rule manifest entries
Registers the components and schema key the codex agent needs for the
Sound page and per-application notification rules, so its branches
compile against a manifest that already holds them rather than each
carrying a conflicting edit to the same file.

An absent notification rule is permissive rather than denying: a newly
installed application must be able to notify without an entry being
written for it first.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 05:54:07 -04:00
Gabriel Brown c415c4f176 fix: harden notification application rule integration 2026-08-18 05:51:32 -04:00
Gabriel Brown 86742da63d Merge Wi-Fi and Bluetooth settings
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 05:50:07 -04:00
Gabriel Brown 3c521cf5fa Handle Wi-Fi and Bluetooth in Settings
Network & Devices was 92 lines and two buttons that opened GNOME. It now
scans, joins, and pairs directly through Quickshell.Networking and
Quickshell.Bluetooth -- NetworkManager and BlueZ over DBus, no shelling
out to nmcli or bluetoothctl. That was the founding requirement for this
desktop: never having to drop to a terminal to join a network.

Scanning follows the page being visible. Wi-Fi scanning and especially
Bluetooth discovery hold the radio, and running either for a list nobody
is looking at spends airtime on nothing.

Joining a secured network gets a real password field, not the clipboard
popover's search box with different placeholder text: a Wi-Fi key typed
into a field that echoes it is readable by anyone behind you, and a
search glyph in front of a password prompt is simply wrong.

Two bugs found by looking at the rendered page, both silent:

The device lookups used enum names that do not exist --
NetworkDeviceType.Wifi rather than DeviceType.Wifi -- so both returned
null and the page reported "No Wi-Fi adapter" on a machine whose Wi-Fi
was connected. Nothing was logged; QML resolves an unknown enum member
to undefined and compares happily.

signalStrength is 0.0-1.0, not a percentage, so thresholds written for
0-100 put every network including the connected one in the bottom
bucket. The labels now use the same buckets as the icons in
quicksettings/WifiList.qml so the two cannot disagree.

The contract compares what the service resolves against what nmcli
reports, rather than only checking that nothing crashed.

Also makes the Home Assistant bridge hermetic: resolve_config read the
user's private env file even when a caller supplied an explicit
environment, so adding a real PANAMA_HOME_ASSISTANT_ENTITIES to that
file silently overrode a fixture asserting the legacy fallback. An
explicit environment is now the whole environment; production still
reads the file. Its live contract skips when no token is configured --
an absent credential is not a defect, and a suite expected to be red
stops being read -- while a configured-but-broken bridge still fails.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 05:50:07 -04:00
Gabriel Brown c087998710 feat: add notification application rules 2026-08-18 05:38:39 -04:00
Gabriel Brown b9800bfbab Scope compositor isolation to reset tests 2026-08-18 03:25:39 -04:00
Gabriel Brown 7b8270fdd6 Keep Settings recovery isolated and display-safe 2026-08-18 03:19:45 -04:00
Gabriel Brown 6d3f888784 Protect live state during restore and reset 2026-08-18 03:10:31 -04:00
Gabriel Brown c2cd79b547 Serialize display recovery checks 2026-08-18 02:55:43 -04:00
Gabriel Brown 0a18290137 Close display verification races 2026-08-18 02:55:43 -04:00
Gabriel Brown efc53435a2 Verify display restoration and expose outputs 2026-08-18 02:55:43 -04:00
Gabriel Brown 67674d297e Polish display confirmation state 2026-08-18 02:55:43 -04:00
Gabriel Brown 2d4b126f14 Harden display apply and recovery 2026-08-18 02:55:43 -04:00
Gabriel Brown 5671324eb2 Make displays configurable, with a revert countdown
Resolution, refresh rate, scale, and rotation, applied through
hl.monitor{} and stored per output.

This is the only setting in Panama where a wrong value can leave the
user unable to SEE the screen well enough to undo it: a mode the panel
cannot show, or a scale that makes everything unreadable, is not
recoverable through the UI that caused it. So a change is never applied
irreversibly. It is applied, then reverted automatically after fifteen
seconds unless confirmed, and confirming is what writes it to the
settings store -- letting the countdown run leaves nothing behind.

The contract tests that property specifically: it applies a scale, waits
out the countdown, and asserts the display came back and that nothing
was stored. A regression there is not a broken feature, it is a user
staring at a blank monitor.

Modes are grouped by resolution with refresh rates beside them. The
panel reports 35, many differing only in refresh-rate rounding -- 60.00
and 59.94 -- which as a flat list of buttons is noise rather than
choice; equal rounded pairs collapse, leaving 21.

Only mode, scale, and transform are configurable. Colour management and
bit depth stay in monitors.lua because they carry a documented screencopy
tradeoff that a settings page cannot explain at the moment you would be
changing it.

Also replaces the display policy rows with the schema-bound ones, so the
page no longer restates labels that PreferenceSchema already holds.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 02:55:43 -04:00
Gabriel Brown 83391e0453 Make settings restore crash-safe 2026-08-18 02:22:23 -04:00
Gabriel Brown 172b099a04 Complete settings snapshot restoration 2026-08-18 02:22:23 -04:00
Gabriel Brown cae72b5179 Make Settings sidebar content scrollable 2026-08-18 01:54:00 -04:00
Gabriel Brown b3b8e0d66d Prevent shortcut reset collisions 2026-08-18 01:47:51 -04:00
Gabriel Brown 787d2b121a Use the durable Home reset boundary 2026-08-18 01:39:34 -04:00
Gabriel Brown 1b323c2fa5 Document Settings completion plan 2026-08-18 01:36:30 -04:00
Gabriel Brown 1ca571458c Complete Panama settings controls 2026-08-18 01:36:07 -04:00
Gabriel Brown 768801dbe4 Fix application role filtering 2026-08-18 01:28:29 -04:00
Gabriel Brown 375ecfcd95 Add application and autostart settings 2026-08-18 01:28:29 -04:00
Gabriel Brown ce95b34d19 Harden Home preferences reset contract 2026-08-18 01:28:29 -04:00
Gabriel Brown 9d430a3079 Finish Home and Phone settings cohesion 2026-08-18 01:28:29 -04:00
Gabriel Brown bac68d2bfb Document the settings vocabulary and its traps
Records how to add a setting -- one schema entry -- and the failure
modes found building the pages. Every one of them fails silently rather
than loudly, which is why they are worth writing down: a wrong readAs
makes successful writes look rejected, hyprpaper accepts an all-outputs
wallpaper request and ignores it, and a copy of the Quickshell config
shares the live shell's ID so an "isolated" harness can kill the running
desktop.

Also documents the two generated configurations and why they are
generated: ~/.config/hypr is a symlink into this repository, so writing
there at runtime would commit machine state to a tracked file.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 01:15:53 -04:00
Gabriel Brown 634a9ebe07 Let shortcuts be rebound from Settings
Every bind in keybinds.lua now goes through a small wrapper that
substitutes the chord from a stored override. Only the chord is taken
from settings; the action is always the Lua value written in that file,
so an override can move a shortcut but can never make one do something
else. That is the property that makes reading them from a file the user
can edit safe, and it is why the alternative -- storing dispatchers --
was not considered.

Overrides are keyed by the shipped chord rather than the description.
Keying by description moved every bind that shared one: rebinding
SUPER+C also moved the XF86Calculator hardware key onto the same chord,
silently costing it. Chords are unique; descriptions are not.

Applying needs hyprctl reload rather than a live hl.bind. Hyprland
reports Lua-defined binds with dispatcher "__lua" and a bytecode offset,
so the action cannot be reconstructed from outside to re-bind it; reload
re-runs the config, which re-reads the settings file.

The capture control ignores modifier-only presses, because every chord
passes through them and holding Super would otherwise be captured the
moment the modifier went down. It refuses a bare letter, which would
swallow ordinary typing, and refuses a key with no keysym name rather
than storing something that would fail to bind. Rebinding onto a chord
already in use is refused rather than shadowing the existing shortcut.

The refactor was verified by snapshotting all 113 binds before and
after: the keymap is byte-identical, and identical again after applying
an override and resetting it.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 01:13:36 -04:00
Gabriel Brown 150f3cdb09 Make the Dock editable and add settings snapshots
The Dock's pinned applications were a sixteen-entry literal in
Settings.qml, so changing what sits in the Dock meant editing QML. They
are now an ordered list in the shared store, with move up, move down,
unpin, and a filtered picker for adding installed applications. Keeping
them in the shared store rather than a file of their own means they are
covered by Restore defaults like everything else.

This needed a "json" schema type for values the schema stores and resets
but does not validate field by field. It exists so structured settings
can live in the one file rather than growing a fourth preference store;
the owning service validates the contents.

Snapshots make the settings app safe to experiment with. The whole
configuration is one file, so a backup is a copy and a restore is an
overwrite, and restoring snapshots what it replaces so it is itself
undoable. A snapshot is validated as JSON before it can be restored over
a working configuration, and a name that is not a plain snapshot
filename from the backup directory is refused.

Snapshot names carry milliseconds. At one-second resolution a save
followed promptly by a restore produced the same filename twice, and the
restore's own safety snapshot overwrote the file it was about to read --
found by the contract, which restores immediately after saving.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 00:59:08 -04:00
Gabriel Brown 6377bfb8fd Route the Applications page and add its settings
Wires the schema entries and routing the codex agent needs for default
applications, weather, vitals refresh, notification timing, and capture,
so its pages can be written against keys that already exist.

Capture directories and encoder arguments are enums rather than free
text. Both are handed to a recorder process, and a settings page has no
reason to expose an arbitrary string there.

ApplicationsPage.qml is a placeholder so the page id can be routed,
registered, and searchable before the real page lands. It is owned by
the other agent and expected to be replaced wholesale.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 00:51:10 -04:00
Gabriel Brown 2bc12e6022 Add wallpaper, power, date, accessibility, and real search
Continues the settings expansion toward replacing GNOME Settings for
everything Panama actually owns.

Wallpaper. A thumbnail grid rather than a path field: the value of this
setting is the picture, so typing a path to something you cannot see is
the worst version of it. Two things about hyprpaper 0.8 shaped this. Its
IPC is much smaller than older documentation suggests -- preload,
listloaded, unload, and reload all answer "invalid hyprpaper request",
so setting is a single call with no preload. And the "<empty>,<path>"
form that used to mean every output is silently ignored, so a wallpaper
set that way appears to succeed and never changes; outputs are walked
explicitly instead. hyprpaper.conf lives in the repo through the
~/.config/hypr symlink and so cannot hold machine state, which is why
the choice lives in the shared settings store and is re-applied at
startup.

Power & Lock. hypridle has no IPC for reconfiguration and its config is
hyprlang rather than the shared JSON, so scripts/panama-idle generates a
config from the settings store and restarts the daemon. The generated
file lives under XDG_STATE_HOME for the same symlink reason, with a
systemd drop-in pointing hypridle at it. Management is a real state and
the page says which one you are in rather than showing sliders that
quietly do nothing. Zero means never for all three timers, which a naive
template would render as "immediately".

Date & Time. Deliberately not stored in Panama's settings: the timezone
and network time belong to the machine and are shared with sessions that
never see this file. Storing a copy would create a second answer to a
question the system already answers. Reads and writes timedatectl
directly; a cancelled polkit prompt surfaces as an error rather than as
a value that appears to have been accepted.

Accessibility. Pointer size and text scale have to agree across three
consumers with no shared configuration -- the compositor, GTK, and the
shell -- so the store is the source of truth and the values are pushed
outward to gsettings and hyprctl setcursor.

Search now indexes the schema instead of the twelve page labels. "gaps",
"wallpaper", and "screenshot" previously found nothing on an app that
has all three, which is the clearest way a settings app feels smaller
than it is. Shortcuts are indexed by what they do. A contract asserts
every non-internal schema label is reachable, so a new setting cannot be
added in an undiscoverable state.

The GNOME delegation allow-list was widened to the panel names
gnome-control-center actually reports; the previous list contained
"users", which is not one of them and so opened nothing.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 00:42:13 -04:00
Gabriel Brown fe7c85e471 Build the settings vocabulary and generate the keymap
Stage 3 and 4 of docs/superpowers/plans/2026-08-17-panama-cohesion.md.

Add SettingsPage plus ToggleRow, SliderRow, ChoiceRow, ActionRow, and
TextRow. A row names a schema key and needs nothing else: label, detail,
range, and unit come from PreferenceSchema, and writes go through
SystemSettings.commitPreference, which routes compositor-backed keys
through apply-and-verify and local keys straight to the store. The page
scaffold that was copy-pasted eleven times is now one component.

Rebuild Appearance around a live preview of the real desktop, scaled by
the ratio between the preview and the actual monitor so a 10px gap on a
4500px display looks as small as it is. Rebuild Desktop & Dock and Input
& Shortcuts on the shared rows, replacing the read-only text that stood
in for controls that were merely expensive to add.

Generate the shortcut list from hyprctl binds. The page held a
hand-typed nineteen entries against a real keymap of a hundred and
thirteen; it could not show the rest and went stale whenever a bind
changed. Every bind now carries its own description -- backfilled for
the twenty-nine that lacked one -- and keybinds-contract.sh fails if any
bind lacks one, since undescribed binds are dropped from the page.

Make Restore defaults span every store Panama owns. Resetting only the
schema store left the Home accessory arrangement customised while
claiming to restore defaults, which is worse than no reset because it is
silent. Done through HomePreferences' existing public aliases rather
than a new API.

Four defects found while building:

cursor:inactive_timeout is answered by getoption as float, not int. A
wrong readAs does not fail loudly; it makes every write to that key look
rejected, and the user saw an error for a change that worked.
schema-hypr-shape-contract.sh now checks all 23 mapped options against
the running compositor.

The Settings window is tiled, so implicitWidth is only a hint and rows
must survive roughly 400px. SliderRow stacks its control under the label
below 520px.

Binding an anchor to undefined to switch layouts does not reliably
release it. Both row layouts are positioned explicitly.

Concurrent compositor writes are queued and merged rather than refused.
The startup replay of every compositor-backed preference routinely
overlaps a UI change, and refusing left the store and the compositor
disagreeing.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 00:24:51 -04:00
Gabriel Brown 8a04e4f9d1 Record live Home accessory verification 2026-08-17 23:33:11 -04:00
Gabriel Brown 5347954a88 Merge branch 'feat/home-accessories-customization'
# Conflicts:
#	config/dot/quickshell/config/qmldir
#	config/dot/quickshell/services/SystemSettings.qml
#	tests/quickshell/settings-pages-contract.sh
2026-08-17 23:31:33 -04:00
Gabriel Brown 00a81edadd Make Panama settings one shared source of truth
Panama had grown into three configuration surfaces that only agreed because
they had been typed to agree: looks.lua hardcoded values, DesktopPreferences
independently defaulted the same values, and SystemSettings replayed them at
startup. Nothing kept them in sync, and the Lua side read no shared state at
all. This lands the first three stages of docs/superpowers/plans/2026-08-17-panama-cohesion.md.

Fix silently failing Hyprland writes. On a Lua-configured Hyprland, hyprctl
keyword refuses the write, prints the refusal to stdout, and still exits 0, so
the HDR, VRR, and direct-scanout toggles persisted their value and reported
success while the compositor never changed. Writes now go through hyprctl eval,
which has the same hazard on syntax and runtime errors, so success is defined
as reading the value back and finding it equal. The existing contract passed
throughout the outage because it re-applied the values already in place; the
new one flips each value to something it does not hold.

Derive preferences from a schema. Every setting used to be restated four times
-- a property alias, a JSON adapter property, a change handler, and a line in
reset -- where omitting any one failed silently. PreferenceSchema.qml is now
the single source, and persistence, validation, reset, and the Hyprland mapping
all derive from it. Unknown keys on disk survive a write so a rollback does not
discard a newer build's settings, and a corrupt file falls back to shipped
defaults. The store moved to ~/.config/panama/settings.json, migrating from the
old state directory without deleting it.

Share that file with Hyprland. prefs.lua reads it at config time with every
shipped literal kept as the fallback, so the config still stands alone. The Lua
is the default, the JSON is the truth, and Settings is the editor. The
compositor-adjustable surface goes from 3 keys to 23.

Also fixes two test-hygiene bugs found by running the suite end to end for the
first time: settings-pages-contract could see the window settings-window-contract
leaves behind, and the new write contract was persisting its deliberately-wrong
values into the user's real store.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-17 23:26:56 -04:00
Gabriel Brown 4bd3638012 Cover partial Phone capabilities 2026-08-17 19:50:27 -04:00
Gabriel Brown 7f540a58e5 Avoid visual Phone harness teardown 2026-08-17 19:42:15 -04:00
Gabriel Brown 7ac5355a77 Record focused verification completion 2026-08-17 19:26:57 -04:00
Gabriel Brown a9eb90f457 Fix Home Assistant composition contract 2026-08-17 19:21:17 -04:00
Gabriel Brown b5c9156e64 Document Home accessory customization 2026-08-17 19:18:45 -04:00
Gabriel Brown 3213a0989a Fix Phone controls runtime accessibility 2026-08-17 19:01:42 -04:00
Gabriel Brown fbf729b25c Add BlueBubbles to Phone controls 2026-08-17 18:48:28 -04:00
Gabriel Brown e37df9d838 Fix Home shelf slider interactions 2026-08-17 18:34:48 -04:00
Gabriel Brown 673aac6aba Build the Home accessory shelf 2026-08-17 18:22:27 -04:00
Gabriel Brown c8eb344a0b Fix Home and Phone settings states 2026-08-17 18:02:09 -04:00
Gabriel Brown 65966200fe Add Home and Phone settings 2026-08-17 17:48:05 -04:00
Gabriel Brown 2130d6a1a5 Block Home actions during fixture transitions 2026-08-17 17:27:43 -04:00
Gabriel Brown 9a99c6064e Fix nested fixture transition draining 2026-08-17 17:16:36 -04:00
Gabriel Brown 4f93522f7d Fix fixture process transitions 2026-08-17 16:59:15 -04:00
Gabriel Brown a325ec807e Fix Home action completion and test isolation 2026-08-17 16:49:50 -04:00
Gabriel Brown c2b19f7545 Compose Home catalog with accessory preferences 2026-08-17 16:35:41 -04:00
Gabriel Brown 925fc0276f Persist Home accessory preferences 2026-08-17 16:24:09 -04:00
Gabriel Brown 65a8197419 Fix Home Assistant catalog QML compatibility 2026-08-17 16:06:05 -04:00
Gabriel Brown ff67387c42 Add Home Assistant light catalog and dimming 2026-08-17 16:01:50 -04:00
Gabriel Brown c42794c5e2 Ignore local feature worktrees 2026-08-17 15:53:11 -04:00
Gabriel Brown 0964af7b73 Plan Home accessory customization 2026-08-17 15:49:20 -04:00
Gabriel Brown 15fb008954 Design Home accessory customization 2026-08-17 14:36:30 -04:00
Gabriel Brown f4c3ffed43 Document the Panama Control Center 2026-08-17 13:47:50 -04:00
Gabriel Brown 5f965daa6a Sanitize Control Center test fixtures 2026-08-17 13:44:23 -04:00
Gabriel Brown 5940d6b4bd Build the Panama Control Center 2026-08-17 13:44:18 -04:00
Gabriel Brown 190e3200e0 Keep offline phone capabilities visible 2026-08-17 13:42:31 -04:00
Gabriel Brown 49ec4d8f6a Add Control Center integration services 2026-08-17 13:31:27 -04:00
Gabriel Brown 173c00fca7 Add the Panama Home Assistant bridge 2026-08-17 13:27:11 -04:00
Gabriel Brown 3a903934c0 Add the Panama KDE Connect bridge 2026-08-17 13:22:31 -04:00
Gabriel Brown dc6da5af34 Plan the Panama Control Center 2026-08-17 13:18:21 -04:00
Gabriel Brown 922395d2b4 Design the Panama Control Center 2026-08-17 13:01:42 -04:00
Gabriel Brown a5450fdb6d Add Ongoing to the Panama Daybook 2026-08-17 12:18:04 -04:00
Gabriel Brown 1ce5c732b7 Document the Panama calendar agenda 2026-08-17 11:49:41 -04:00
Gabriel Brown f9cc15fb96 Fix GNOME Calendar date handoff 2026-08-17 11:48:11 -04:00
Gabriel Brown 2728fb08fb Make shell restart tests reliable 2026-08-17 11:45:03 -04:00
Gabriel Brown f27a1dca11 Add the quiet calendar capsule 2026-08-17 11:41:58 -04:00
Gabriel Brown 03914e23af Build the Prism calendar Daybook 2026-08-17 11:38:24 -04:00
Gabriel Brown 6a1bec849d Route the interactive Panama date menu 2026-08-17 11:32:57 -04:00
Gabriel Brown fb27c5b309 Add live calendar state to Quickshell 2026-08-17 11:31:15 -04:00
Gabriel Brown d37d4a87de Add the EDS calendar bridge 2026-08-17 11:28:12 -04:00
Gabriel Brown 530a5590a6 Plan the Panama calendar agenda 2026-08-17 11:15:19 -04:00
Gabriel Brown e92c2ee5f9 Design the Panama calendar agenda 2026-08-17 11:04:05 -04:00
Gabriel Brown 5248883e4b Build the Panama Hyprland desktop 2026-08-17 10:32:55 -04:00
Gabriel Brown 67033f2a31 update kitty conf 2026-07-17 15:41:03 -04:00
Gabriel Brown 2dc9676822 Comment out docker host bc that would break server actually 2026-06-30 03:37:16 -04:00
Gabriel Brown 89666e7485 Add update command & podman host to bash 2026-06-30 03:36:22 -04:00
Gabriel 7123bb4885 add alias for resume in claude 2026-06-05 11:09:45 -04:00
Gabriel 7461c88b98 Update nvim config & claude alias 2026-06-05 11:06:09 -04:00
gib 11202d06d9 Add an automatic tmux session when using ssh 2026-06-02 08:22:22 -05:00
Gabriel Brown ac57cfa319 Updates 2026-06-02 09:19:42 -04:00
Gabriel Brown 4c529afae7 Cleanup new nvim config 2026-05-19 12:28:26 -05:00
Gabriel Brown 372a0f60e5 Update neovim config to use lazyvim 2026-05-16 14:26:09 -04:00
Gabriel Brown 19b8ec2542 Update highlighted color in markdown so I can see it! 2026-05-14 11:42:59 -04:00
gib 210bdbdddc Brought back supermavengit add -A! 2026-04-23 09:21:51 -04:00
gib 47137191dd Update nvim config 2026-04-18 15:29:43 -04:00
gib ef2d49fbaa Fix markdown tabs 2026-04-15 20:00:57 -04:00
gib 065b7747cf Update neovim lsp config to work better with monorepo 2026-04-14 20:04:12 -04:00
gib cfc2b82c06 Update vim stuff 2026-04-07 08:24:08 -04:00
gib e985792472 Update stuff 2026-03-28 15:09:20 -05:00
gib d9ee1ddce4 Changes since using laptop 2026-03-25 10:04:49 -05:00
gib 4fcfb9c45e idk 2026-01-03 11:23:56 -06:00
gib 50ea51263a Update dotfiles 2025-12-31 20:28:19 -06:00
gib 53bec8ddbc Update some configs & stuff 2025-12-30 18:00:30 -06:00
gib b8ce2aef5c Add gtk files 2025-12-29 15:41:28 -06:00
gib 7c55264138 Update nvim config & add alias for leetcode plugin 2025-12-25 18:22:12 -06:00
gib 6150274903 Neovim is looking sick now 2025-12-22 13:37:33 -06:00
gib c0ba11768a Add stuff to install script & clean up lsp config 2025-12-22 11:47:38 -06:00
gib fba92951f9 Remove copilot 2025-12-22 11:14:10 -06:00
gib 11863984ca More stuff from laptop 2025-12-22 10:51:54 -06:00
gib e4f1fe8e05 Update from work I did on laptop 2025-12-22 10:46:59 -06:00
gib 18cfd8b174 Update config & make sure avante is set up nicely 2025-12-20 02:54:43 -06:00
gib ca7d4e3460 Update script to be really pretty 2025-12-18 21:08:19 +00:00
gib 3bb192238c Update script to be really pretty 2025-12-18 21:03:43 +00:00
gib cba2ca3b1c Update & clean some stuff up 2025-12-18 20:35:11 +00:00
gib 81eefc4f84 Update stuff 2025-12-18 14:17:09 -06:00
gib 5f73b76800 Add more stuff 2025-11-13 16:59:59 -06:00
954 changed files with 187491 additions and 1539 deletions
+20 -3
View File
@@ -1,6 +1,23 @@
# Ignore Wireguard config of course!
/config/wg/**
# Ignore bash environment variables.
/config/bash/env
# Ignore backups of old config files
/config/old/**
/config/old
# Ignore Wireguard config of course!
/config/wg/**
# Ignore Neovim lazy-lock file
/config/dot/nvim/lazy-lock.json
# Local visual-companion sessions and generated design mocks.
/.superpowers/
# Local linked worktrees used for reviewed feature development.
/.worktrees/
# Python helper bytecode is local runtime state.
__pycache__/
*.py[cod]
# Generated from the colour scheme; machine state, not configuration.
/config/dot/kitty/current-theme.conf
# Generated from the colour scheme; machine state, not configuration.
/config/dot/gtk-3.0/settings.ini
/config/dot/gtk-4.0/settings.ini
/config/dot/tmux/current-theme.conf
+61 -1
View File
@@ -1,2 +1,62 @@
# Panama
## Formerly Sunhat, this is a personal config for Fedora, with the intention of helping a user set up their Fedora system with one command.
Formerly Sunhat. A personal config for Fedora, with the intention of helping a
user set up their Fedora system with one command.
```sh
git clone https://git.gbrown.org/gib/Panama.git ~/.local/share/Panama
~/.local/share/Panama/install
```
`install` runs everything in `setup/scripts/` in order:
| Script | Does |
|---|---|
| `install-packages` | Repos (RPM Fusion, Terra, Hyprland COPR), then the four package lists in `setup/packages/` |
| `link-dotfiles` | Symlinks `config/dot/<name>` → `~/.config/<name>` |
| `change-settings` | Copies `config/copy/` over `/`, applies gsettings, enables user services |
Existing configs are moved to `config/old/` rather than overwritten.
## Desktops
Panama configures **two** desktops that coexist. Both sessions stay available in
GDM, so you can switch back and forth while you settle in.
### GNOME
The original setup: Forge for tiling, Dash-to-Dock, Openbar, Vitals,
AppIndicator support. Configured through `config/dot/forge/` and gsettings.
### Hyprland
A from-scratch replacement built to reproduce the GNOME setup closely enough
that muscle memory transfers — same keybinds, same panel contents, same dock,
same Tokyo Night Moon palette.
| Piece | What it is |
|---|---|
| `config/dot/hypr/` | Compositor config. **Lua, not hyprlang** — see its README |
| `config/dot/quickshell/` | The shell: bar, dock, Continuum overview, Settings, Screen Intelligence, focus sessions, quick settings, notifications, screenshot UI |
| `config/dot/vicinae/` | Raycast-style launcher, themed |
| `config/dot/uwsm/` | Session environment (see the uwsm caveat in the hypr README) |
| `config/dot/wofi/` | Fallback launcher, in case the shell fails to start |
| `config/dot/xdg-desktop-portal/` | Portal backend routing |
**Start here: [`config/dot/hypr/README.md`](config/dot/hypr/README.md)** — it
covers the Lua migration, the uwsm environment gotcha, the HDR decision, the
full keymap, and troubleshooting.
Log in as **"Hyprland (uwsm-managed)"**, not plain "Hyprland".
## Layout
```
bin/ Small user-facing commands on PATH
config/
bash/ .bashrc, aliases, env (env is gitignored)
copy/ Files copied verbatim over / (needs sudo)
dot/ Symlinked into ~/.config
old/ Backups of whatever was replaced (gitignored)
setup/
packages/ One package per line
scripts/ Run in order by ./install
```
Executable
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
import os
import sys
import argparse
from pathlib import Path
import pyperclip
import questionary
# List of directories to exclude
EXCLUDED_DIRS = {'node_modules', '.next', '.venv', '.git', '__pycache__', '.idea', '.vscode', 'ui'}
def collect_files(project_path):
"""
Collects files from the project directory, excluding specified directories and filtering by extensions.
Returns a list of file paths relative to the project directory.
"""
collected_files = []
for root, dirs, files in os.walk(project_path):
# Exclude specified directories
dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS]
for file in files:
file_path = Path(root) / file
relative_path = file_path.relative_to(project_path)
collected_files.append(relative_path)
return collected_files
def main():
# Parse command-line arguments
parser = argparse.ArgumentParser(description='Generate Markdown from selected files.')
parser.add_argument('path', nargs='?', default='.', help='Path to the project directory')
args = parser.parse_args()
project_path = Path(args.path).resolve()
if not project_path.is_dir():
print(f"Error: '{project_path}' is not a directory.")
sys.exit(1)
# Collect files from the project directory
file_list = collect_files(project_path)
if not file_list:
print("No files found in the project directory with the specified extensions.")
sys.exit(1)
# Sort file_list for better organization
file_list.sort()
# Interactive file selection using questionary
print("\nSelect the files you want to include:")
selected_files = questionary.checkbox(
"Press space to select files, and Enter when you're done:",
choices=[str(f) for f in file_list]
).ask()
if not selected_files:
print("No files selected.")
sys.exit(1)
# Generate markdown
markdown_lines = []
markdown_lines.append('')
for selected_file in selected_files:
file_path = project_path / selected_file
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Determine the language for code block from file extension
language = file_path.suffix.lstrip('.')
markdown_lines.append(f'{selected_file}')
markdown_lines.append(f'```{language}')
markdown_lines.append(content)
markdown_lines.append('```')
markdown_lines.append('')
except Exception as e:
print(f"Error reading file {selected_file}: {e}")
markdown_text = '\n'.join(markdown_lines)
# Copy markdown content to clipboard
pyperclip.copy(markdown_text)
print("Markdown content has been copied to the clipboard.")
if __name__ == "__main__":
# Check if required libraries are installed
try:
import questionary
import pyperclip
except ImportError as e:
missing_module = e.name
print(f"Error: Missing required module '{missing_module}'.")
print(f"Please install it by running: pip install {missing_module}")
sys.exit(1)
main()
Executable
+227
View File
@@ -0,0 +1,227 @@
#!/usr/bin/env bash
#
# panama – Helper for managing the Panama dotfiles/repo
# Version 1.0
# Author: Gabriel Brown
#
# Commands:
# update Commit & sync local changes (or just pull if clean)
# edit Open the Panama repo in Neovim
# help Show this help
#
# Designed to grow: add new subcommands as cmd_<name> functions and
# register them in the dispatcher / usage block below.
set -euo pipefail
PROGRAM=$(basename "$0")
VERSION="1.0"
# ----------------------------------------------------------------------------
# Locate the Panama repo (this script lives in <repo>/bin/panama)
# ----------------------------------------------------------------------------
SCRIPT_PATH=$(readlink -f "${BASH_SOURCE[0]}")
PANAMA_DIR=$(cd "$(dirname "$SCRIPT_PATH")/.." && pwd)
# ----------------------------------------------------------------------------
# Pretty output
# ----------------------------------------------------------------------------
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1 && [[ $(tput colors 2>/dev/null || echo 0) -ge 8 ]]; then
BOLD=$(tput bold); RESET=$(tput sgr0)
RED=$(tput setaf 1); GREEN=$(tput setaf 2); YELLOW=$(tput setaf 3)
BLUE=$(tput setaf 4); MAGENTA=$(tput setaf 5); CYAN=$(tput setaf 6)
else
BOLD=""; RESET=""; RED=""; GREEN=""; YELLOW=""; BLUE=""; MAGENTA=""; CYAN=""
fi
info() { printf '%s==>%s %s\n' "${BLUE}${BOLD}" "$RESET" "$*"; }
ok() { printf '%s✓%s %s\n' "${GREEN}${BOLD}" "$RESET" "$*"; }
warn() { printf '%s!%s %s\n' "${YELLOW}${BOLD}" "$RESET" "$*"; }
err() { printf '%s✗%s %s\n' "${RED}${BOLD}" "$RESET" "$*" >&2; }
header(){ printf '\n%s%s%s\n' "${MAGENTA}${BOLD}" "$*" "$RESET"; }
# Ask a yes/no question. Returns 0 for yes, 1 for no. Default = no.
confirm() {
local prompt="$1" reply
printf '%s?%s %s %s[y/N]%s ' "${CYAN}${BOLD}" "$RESET" "$prompt" "$BOLD" "$RESET"
read -r reply || true
[[ "$reply" =~ ^[Yy]([Ee][Ss])?$ ]]
}
# ----------------------------------------------------------------------------
# Usage
# ----------------------------------------------------------------------------
usage() {
cat <<EOF
${BOLD}$PROGRAM${RESET} – manage the Panama repo (${PANAMA_DIR})
${BOLD}Usage:${RESET}
$PROGRAM <command> [options]
${BOLD}Commands:${RESET}
${GREEN}update${RESET} Review, commit & sync local changes. If the working tree is
clean it simply runs 'git pull'.
${GREEN}edit${RESET} Open the Panama repo in Neovim.
${GREEN}help${RESET} Show this help (also -h, --help).
${BOLD}Options:${RESET}
-h, --help Show this help and exit
--version Show version and exit
${BOLD}Examples:${RESET}
$PROGRAM update
$PROGRAM edit
EOF
}
# ----------------------------------------------------------------------------
# Guard: make sure we're in a git repo
# ----------------------------------------------------------------------------
require_git_repo() {
if ! git -C "$PANAMA_DIR" rev-parse --git-dir >/dev/null 2>&1; then
err "'$PANAMA_DIR' is not a git repository."
exit 1
fi
}
# ----------------------------------------------------------------------------
# Command: update
# ----------------------------------------------------------------------------
cmd_update() {
require_git_repo
cd "$PANAMA_DIR"
info "Panama repo: ${BOLD}${PANAMA_DIR}${RESET}"
# Any changes in the working tree? (modified, staged, or untracked)
if [[ -z "$(git status --porcelain)" ]]; then
info "Working tree is clean — pulling latest changes."
if git pull --ff-only; then
ok "Already in sync."
else
err "git pull failed."
exit 1
fi
return
fi
# Show what changed
header "Changed files"
git -c color.status=always status --short
header "Diff"
# Tracked changes (staged + unstaged) relative to the last commit.
git --no-pager -c color.diff=always diff HEAD
# Untracked files won't appear in 'git diff', so show them as new files.
local untracked
untracked=$(git ls-files --others --exclude-standard)
if [[ -n "$untracked" ]]; then
while IFS= read -r f; do
[[ -z "$f" ]] && continue
git --no-pager -c color.diff=always diff --no-index -- /dev/null "$f" || true
done <<< "$untracked"
fi
echo
if ! confirm "Commit these changes?"; then
warn "Aborted — no changes committed."
return
fi
# Commit message
local msg
printf '%s?%s Commit message: ' "${CYAN}${BOLD}" "$RESET"
read -r msg || true
if [[ -z "${msg// }" ]]; then
msg="Update $(date '+%Y-%m-%d %H:%M:%S')"
warn "No message given — using: ${BOLD}${msg}${RESET}"
fi
# Is the local branch up to date with its upstream?
info "Checking whether the repo is up to date..."
if git rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1; then
git fetch --quiet
local local_rev remote_rev base_rev
local_rev=$(git rev-parse @)
remote_rev=$(git rev-parse '@{u}')
base_rev=$(git merge-base @ '@{u}')
if [[ "$local_rev" == "$remote_rev" ]]; then
ok "Repo is up to date."
elif [[ "$local_rev" == "$base_rev" ]]; then
warn "Repo is behind upstream — stashing, pulling, then re-applying."
info "Stashing local changes..."
git stash push --include-untracked -m "panama-update-$(date +%s)" >/dev/null
if ! git pull --ff-only; then
err "git pull failed — restoring your changes."
git stash pop || true
exit 1
fi
info "Re-applying stashed changes..."
if ! git stash pop; then
err "Conflict while re-applying changes. Resolve it, then commit manually."
exit 1
fi
else
warn "Local branch has diverged from upstream — committing locally only."
fi
else
warn "No upstream configured for this branch — committing locally only."
fi
# Commit everything
info "Committing changes..."
git add -A
git commit -m "$msg"
ok "Committed: ${BOLD}${msg}${RESET}"
echo
if confirm "Push the changes now?"; then
info "Pushing..."
if git push; then
ok "Pushed to remote."
else
err "git push failed."
exit 1
fi
else
info "Done — changes committed locally but not pushed."
fi
}
# ----------------------------------------------------------------------------
# Command: edit
# ----------------------------------------------------------------------------
cmd_edit() {
if ! command -v nvim >/dev/null 2>&1; then
err "Neovim (nvim) is not installed or not on PATH."
exit 1
fi
info "Opening ${BOLD}${PANAMA_DIR}${RESET} in Neovim."
cd "$PANAMA_DIR"
exec nvim .
}
# ----------------------------------------------------------------------------
# Dispatcher
# ----------------------------------------------------------------------------
main() {
local cmd="${1:-}"
case "$cmd" in
update) shift; cmd_update "$@" ;;
edit) shift; cmd_edit "$@" ;;
help|-h|--help|"") usage ;;
--version) printf '%s %s\n' "$PROGRAM" "$VERSION" ;;
*)
err "Unknown command: '$cmd'"
echo
usage
exit 1
;;
esac
}
main "$@"
Executable
BIN
View File
Binary file not shown.
+5
View File
@@ -24,3 +24,8 @@ else
fi
export PATH
fi
. "$HOME/.cargo/env"
# >>> Codex installer >>>
export PATH="/home/gib/.local/bin:$PATH"
# <<< Codex installer <<<
+6
View File
@@ -10,6 +10,11 @@ alias shutdown="systemctl poweroff"
alias update-grub="sudo grub-mkconfig -o /etc/grub2-efi.cfg"
alias nvidia-smi-docker='sudo docker run --rm --gpus all --privileged nvidia/cuda:12.8.1-base-ubuntu24.04 nvidia-smi'
alias ncconnect='sudo docker exec -u www-data -it nextcloud-aio-nextcloud bash'
alias avante='nvim -c "lua vim.defer_fn(function()require(\"avante.api\").zen_mode()end, 100)"'
alias nlc='nvim leetcode.nvim'
alias clauded='claude --allow-dangerously-skip-permissions'
alias claudedr='claude --allow-dangerously-skip-permissions --resume'
alias commit-repo='git add -A && git commit -m "Update stuff" && git push'
# Docker Shortcuts
alias docker-up='sudo docker compose up -d'
@@ -35,6 +40,7 @@ alias ...='cd ../..'
alias ....='cd ../../..'
# Tools
alias vim='vimx'
alias n='nvim'
alias n.="nvim ."
alias ns="nvim src"
+17 -4
View File
@@ -16,13 +16,26 @@ export ANDROID_SDK_HOME="$HOME/.local/share/Android"
export GOPATH="$HOME/.local/share/go"
export DOTNETPATH="$HOME/.dotnet/tools"
# Podman
# export DOCKER_HOST=unix:///run/user/1000/podman/podman.sock
# Set complete path
export PATH="$HOME/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PANAMA_PATH/bin:$BUN_INSTALL/bin:$CARGO_PATH/bin:$PNPM_HOME:$PYENV_ROOT/bin:$HOME/.rbenv/bin:/usr/lib/ccache/bin/:$GOPATH/bin:$DOTNETPATH"
export PATH="$HOME/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PANAMA_PATH/bin:$BUN_INSTALL/bin:$CARGO_PATH/bin:$PNPM_HOME/bin:$PYENV_ROOT/bin:$HOME/.rbenv/bin:/usr/lib/ccache/bin/:$GOPATH/bin:$DOTNETPATH"
# Nvm
set -h
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
source /etc/profile.d/nvm.sh
# Auto-switch Node version when entering a directory with .nvmrc
_nvm_auto_use() {
if [[ -f .nvmrc ]]; then
nvm use --silent
fi
}
export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }_nvm_auto_use"
# Auto-start or attach tmux for SSH interactive shells
if [[ -n "$SSH_CONNECTION" && -z "$TMUX" && $- == *i* ]]; then
exec tmux new-session -A -s main
fi
# Zoxide
eval "$(zoxide init bash)"
+8
View File
@@ -0,0 +1,8 @@
# see `man dnf.conf` for defaults and possible options
[main]
fastestmirror=True
max_parallel_downloads=10
defaultyes=True
keepcache=True
deltarpm=True
@@ -0,0 +1,18 @@
# Stable, colon-free symlinks for the DRM devices.
#
# Why this exists: Hyprland's AQ_DRM_DEVICES selects which GPU renders, but
# - /dev/dri/cardN numbering is assigned at boot and is not stable, and
# - /dev/dri/by-path/ names contain colons, which AQ_DRM_DEVICES itself uses
# as its separator, so they cannot be used.
#
# These rules are keyed to specific PCI addresses and simply do not match on
# other machines; hypr/env.lua checks that the symlink exists before setting
# AQ_DRM_DEVICES, so a non-match is harmless.
#
# Apply without rebooting: sudo udevadm control --reload && sudo udevadm trigger
# desktop-gib: Radeon RX 7800 XT (discrete, drives DP-2)
KERNEL=="card*", KERNELS=="0000:03:00.0", SUBSYSTEM=="drm", SUBSYSTEMS=="pci", SYMLINK+="dri/amd-dgpu"
# desktop-gib: Granite Ridge integrated graphics
KERNEL=="card*", KERNELS=="0000:12:00.0", SUBSYSTEM=="drm", SUBSYSTEMS=="pci", SYMLINK+="dri/amd-igpu"
@@ -0,0 +1,614 @@
"""nautilus extension: nautilus_open_any_terminal"""
# based on: https://github.com/gnunn1/tilix/blob/master/data/nautilus/open-tilix.py
import ast
import re
import shlex
from dataclasses import dataclass, field
from functools import cache
from gettext import gettext, translation
from os.path import expanduser
from subprocess import Popen
from typing import Optional
from urllib.parse import quote, unquote, urlparse
from gi import require_version
try:
require_version("Nautilus", "4.1")
except ValueError:
require_version("Nautilus", "4.0")
require_version("Gtk", "4.0")
from gi.repository import Nautilus as FileManager
API_VERSION = "4.1"
from gi.repository import Gio, GLib, GObject, Gtk # noqa: E402 pylint: disable=wrong-import-position
@dataclass(frozen=True)
class Terminal:
"""Data class representing a terminal configuration."""
name: str
workdir_arguments: Optional[list[str]] = None
new_tab_arguments: Optional[list[str]] = None
new_window_arguments: Optional[list[str]] = None
command_arguments: list[str] = field(default_factory=lambda: ["-e"])
flatpak_package: Optional[str] = None
_ = gettext
for localedir in [expanduser("~/.local/share/locale"), "/usr/share/locale"]:
try:
trans = translation("nautilus-open-any-terminal", localedir)
trans.install()
_ = trans.gettext
break
except FileNotFoundError:
continue
TERMINALS = {
"alacritty": Terminal("Alacritty"),
"app2unit-term": Terminal("app2unit-term"),
"blackbox": Terminal(
"Black Box",
workdir_arguments=["--working-directory"],
command_arguments=["-c"],
flatpak_package="com.raggesilver.BlackBox",
),
"blackbox-terminal": Terminal(
"Black Box",
workdir_arguments=["--working-directory"],
command_arguments=["-c"],
),
"bobcat": Terminal(
"Bobcat",
workdir_arguments=["--working-dir"],
command_arguments=["--"],
),
"cool-retro-term": Terminal("cool-retro-term", workdir_arguments=["--workdir"]),
"custom": Terminal(_("Terminal"), command_arguments=[]),
"contour": Terminal(
"Contour",
workdir_arguments=["--working-directory"],
flatpak_package="org.contourterminal.Contour",
),
"cosmic-term": Terminal("COSMIC Terminal"),
"deepin-terminal": Terminal("Deepin Terminal"),
"ddterm": Terminal(
"Drop down Terminal extension",
workdir_arguments=["--working-directory"],
flatpak_package="com.github.amezin.ddterm",
),
"foot": Terminal("Foot"),
"footclient": Terminal("FootClient"),
"ghostty": Terminal("Ghostty"),
"gnome-terminal": Terminal("Terminal", new_tab_arguments=["--tab"], command_arguments=["--"]),
"guake": Terminal("Guake", workdir_arguments=["--show", "--new-tab"]),
"kermit": Terminal("Kermit"),
"kgx": Terminal("Console", new_tab_arguments=["--tab"]),
"kitty": Terminal("Kitty"),
"konsole": Terminal("Konsole", new_tab_arguments=["--new-tab"]),
"mate-terminal": Terminal("Mate Terminal", new_tab_arguments=["--tab"]),
"mlterm": Terminal("Mlterm"),
"ptyxis": Terminal(
"Ptyxis",
workdir_arguments=["-d"],
command_arguments=["--"],
new_tab_arguments=["--tab"],
new_window_arguments=["--new-window"],
flatpak_package="app.devsuite.Ptyxis",
),
"ptyxis-nightly": Terminal(
"Ptyxis",
workdir_arguments=["-d"],
command_arguments=["--"],
new_tab_arguments=["--tab"],
new_window_arguments=["--new-window"],
flatpak_package="org.gnome.Ptyxis.Devel",
),
"qterminal": Terminal("QTerminal"),
"rio": Terminal("Rio"),
"sakura": Terminal("Sakura"),
"st": Terminal("Simple Terminal"),
"tabby": Terminal("Tabby", command_arguments=["run"], workdir_arguments=["open"]),
"terminator": Terminal("Terminator", new_tab_arguments=["--new-tab"]),
"terminology": Terminal("Terminology"),
"terminus": Terminal("Terminus"),
"termite": Terminal("Termite"),
"tilix": Terminal("Tilix", flatpak_package="com.gexperts.Tilix"),
"urxvt": Terminal("rxvt-unicode"),
"urxvtc": Terminal("urxvtc"),
"uwsm-terminal": Terminal("uwsm-terminal"),
"uxterm": Terminal("UXTerm"),
"warp": Terminal(
"Warp",
new_tab_arguments=["--virtual-arg-for-tabs"], # This is just to indicate tab support
),
"wezterm": Terminal(
"Wez's Terminal Emulator",
workdir_arguments=["--cwd"],
new_tab_arguments=["start", "--new-tab"],
new_window_arguments=["start"],
flatpak_package="org.wezfurlong.wezterm",
),
"xfce4-terminal": Terminal("Xfce Terminal", new_tab_arguments=["--tab"]),
"xterm": Terminal("XTerm"),
}
FLATPAK_PARMS = ["off", "system", "user"]
terminal = "gnome-terminal"
terminal_cmd: list[str] = None # type: ignore
terminal_data: Terminal = TERMINALS["gnome-terminal"]
new_tab = False
flatpak = FLATPAK_PARMS[0]
custom_local_command: str
custom_remote_command: str
GSETTINGS_PATH = "com.github.stunkymonkey.nautilus-open-any-terminal"
GSETTINGS_KEYBINDINGS = "keybindings"
GSETTINGS_BIND_REMOTE = "bind-remote"
GSETTINGS_TERMINAL = "terminal"
GSETTINGS_NEW_TAB = "new-tab"
GSETTINGS_FLATPAK = "flatpak"
GSETTINGS_USE_GENERIC_TERMINAL_NAME = "use-generic-terminal-name"
GSETTINGS_CUSTOM_LOCAL_COMMAND = "custom-local-command"
GSETTINGS_CUSTOM_REMOTE_COMMAND = "custom-remote-command"
REMOTE_URI_SCHEME = ["ftp", "sftp"]
# Adapted from https://www.freedesktop.org/software/systemd/man/latest/os-release.html
def read_os_release():
"""Read and parse the OS release information."""
possible_os_release_paths = ["/etc/os-release", "/usr/lib/os-release"]
for file_path in possible_os_release_paths:
try:
with open(file_path, mode="r", encoding="utf-8") as os_release:
for line_number, line in enumerate(os_release, start=1):
line = line.rstrip()
if not line or line.startswith("#"):
continue
result = re.match(r"([A-Z][A-Z_0-9]+)=(.*)", line)
if result:
name, val = result.groups()
if val and val[0] in "\"'":
val = ast.literal_eval(val)
yield name, val
else:
raise OSError(f"{file_path}:{line_number}: bad line {line!r}")
except FileNotFoundError:
continue
@cache
def distro_id() -> set[str]:
"""get the set of distribution ids"""
try:
os_release = dict(read_os_release())
except OSError:
return set(["unknown"])
ids = [os_release["ID"]]
if id_like := os_release.get("ID_LIKE"):
ids.extend(id_like.split(" "))
return set(ids)
def parse_custom_command(command: str, data: str | list[str]) -> list[str]:
"""Substitute every '%s' in the command with data and split it into arguments"""
if isinstance(data, str):
data = [data]
return shlex.split(command.replace("%s", shlex.join(data)))
def run_command_in_terminal(command: list[str], *, cwd: str | None = None):
if terminal == "custom":
cmd = parse_custom_command(custom_remote_command, command)
else:
cmd = terminal_cmd.copy()
if cwd and terminal_data.workdir_arguments:
cmd.extend(terminal_data.workdir_arguments)
cmd.append(cwd)
cmd.extend(terminal_data.command_arguments)
cmd.extend(command)
Popen(cmd, cwd=cwd) # pylint: disable=consider-using-with
def ssh_command_from_uri(uri: str, *, is_directory: bool):
"""Creates an ssh command that executes or cd's into remote uri"""
result = urlparse(uri)
cmd = ["ssh", "-t"]
if result.username:
cmd.append(f"{result.username}@{result.hostname}")
else:
cmd.append(result.hostname) # type: ignore
if result.port:
cmd.append("-p")
cmd.append(str(result.port))
target = shlex.quote(unquote(result.path))
if is_directory:
cmd.extend(["cd", target, ";", "exec", "${SHELL:-/bin/sh}", "-l"])
else:
cmd.extend(["exec", target])
return cmd
def open_remote_terminal_in_uri(uri: str):
"""Open a new remote terminal"""
run_command_in_terminal(ssh_command_from_uri(uri, is_directory=True))
def open_local_terminal_in_uri(uri: str):
"""open the new terminal with correct path"""
result = urlparse(uri)
filename = unquote(result.path)
if result.scheme == "admin":
run_command_in_terminal(["sudo", "-s"], cwd=filename)
return
if terminal == "warp":
# Force new_tab to be considered even without traditional tab arguments
Popen( # pylint: disable=consider-using-with
["xdg-open", f"warp://action/new_{'tab' if new_tab else 'window'}?path={result.path}"]
)
return
cmd = terminal_cmd.copy()
if terminal == "custom":
cmd = parse_custom_command(custom_local_command, filename)
elif filename and terminal_data.workdir_arguments:
cmd.extend(terminal_data.workdir_arguments)
cmd.append(filename)
Popen(cmd, cwd=filename) # pylint: disable=consider-using-with
def directory_menu_item_id(*, foreground: bool, remote: bool):
return f"OpenTerminal::open{'_' if foreground else '_bg_'}{'remote' if remote else 'file'}_item"
def executable_menu_item_id(*, remote: bool):
return f"OpenTerminal::execute{'_remote_' if remote else '_file_'}item"
def get_directory_menu_items(
file: FileManager.FileInfo, callback, *, foreground: bool, terminal_name: str | None = None
):
items = []
remote = file.get_uri_scheme() in REMOTE_URI_SCHEME
terminal_name = terminal_name or terminal_data.name
if remote:
if foreground:
REMOTE_LABEL = _("Open in Remote {}")
REMOTE_TIP = _("Open Remote {} in {}")
LOCAL_LABEL = _("Open in Local {}")
LOCAL_TIP = _("Open Local {} in {}")
tip = REMOTE_TIP.format(terminal_name, file.get_name())
else:
REMOTE_LABEL = _("Open Remote {} Here")
REMOTE_TIP = _("Open Remote {} in This Directory")
LOCAL_LABEL = _("Open Local {} Here")
LOCAL_TIP = _("Open Local {} in This Directory")
tip = REMOTE_TIP.format(terminal_name)
item = FileManager.MenuItem(
name=directory_menu_item_id(foreground=foreground, remote=True),
label=REMOTE_LABEL.format(terminal_name),
tip=tip,
)
item.connect("activate", callback, file, True)
items.append(item)
elif foreground:
LOCAL_LABEL = _("Open in {}")
LOCAL_TIP = _("Open {} in {}")
else:
LOCAL_LABEL = _("Open {} Here")
LOCAL_TIP = _("Open {} in This Directory")
# Let wezterm handle opening a local terminal
if terminal == "wezterm" and flatpak == "off":
return items
if foreground:
tip = LOCAL_TIP.format(terminal_name, file.get_name())
else:
tip = LOCAL_TIP.format(terminal_name)
item = FileManager.MenuItem(
name=directory_menu_item_id(foreground=foreground, remote=False),
label=LOCAL_LABEL.format(terminal_name),
tip=tip,
)
item.connect("activate", callback, file, False)
items.append(item)
return items
def get_executable_menu_items(file: FileManager.FileInfo, callback, *, terminal_name: str | None = None):
items = []
remote = file.get_uri_scheme() in REMOTE_URI_SCHEME
terminal_name = terminal_name or terminal_data.name
if remote:
REMOTE_LABEL = _("Execute in Remote {}")
REMOTE_TIP = _("Execute {} in {} via SSH")
LOCAL_LABEL = _("Execute in Local {}")
LOCAL_TIP = _("Execute {} in Local {}")
tip = REMOTE_TIP.format(file.get_name(), terminal_name)
item = FileManager.MenuItem(
name=executable_menu_item_id(remote=True),
label=REMOTE_LABEL.format(terminal_name),
tip=tip,
)
item.connect("activate", callback, file, True)
items.append(item)
else:
LOCAL_LABEL = _("Execute in {}")
LOCAL_TIP = _("Execute {} in {}")
tip = LOCAL_TIP.format(file.get_name(), terminal_name)
item = FileManager.MenuItem(
name=executable_menu_item_id(remote=False),
label=LOCAL_LABEL.format(terminal_name),
tip=tip,
)
item.connect("activate", callback, file, False)
items.append(item)
return items
def is_executable(file: Gio.File) -> bool:
try:
attributes = file.query_info("access::can-execute", Gio.FileQueryInfoFlags.NONE)
except GLib.Error:
return False
return attributes.get_attribute_boolean("access::can-execute")
def set_terminal_args(*_args):
# pylint: disable=possibly-used-before-assignment
"""set the terminal_cmd to the correct values"""
global new_tab
global flatpak
global terminal_cmd
global terminal_data
global custom_local_command
global custom_remote_command
value = _gsettings.get_string(GSETTINGS_TERMINAL)
newer_tab = _gsettings.get_boolean(GSETTINGS_NEW_TAB)
flatpak = FLATPAK_PARMS[_gsettings.get_enum(GSETTINGS_FLATPAK)]
new_terminal_data = TERMINALS.get(value)
if not new_terminal_data:
print(f'open-any-terminal: unknown terminal "{value}"')
return
global terminal
terminal = value
terminal_data = new_terminal_data
if newer_tab and terminal_data.new_tab_arguments:
new_tab = newer_tab
new_tab_text = "opening in a new tab"
else:
new_tab_text = "opening a new window"
if newer_tab and not terminal_data.new_tab_arguments:
new_tab_text += " (terminal does not support tabs)"
if flatpak != FLATPAK_PARMS[0] and terminal_data.flatpak_package is not None:
terminal_cmd = ["flatpak", "run", "--" + flatpak, terminal_data.flatpak_package]
flatpak_text = f"with flatpak as {flatpak}"
else:
terminal_cmd = [terminal]
if terminal == "blackbox" and "fedora" in distro_id():
# It's called like this on fedora
terminal_cmd[0] = "blackbox-terminal"
flatpak = FLATPAK_PARMS[0]
flatpak_text = ""
if terminal == "custom":
terminal_cmd = []
custom_local_command = _gsettings.get_string(GSETTINGS_CUSTOM_LOCAL_COMMAND)
custom_remote_command = _gsettings.get_string(GSETTINGS_CUSTOM_REMOTE_COMMAND)
elif new_tab and terminal_data.new_tab_arguments:
terminal_cmd.extend(terminal_data.new_tab_arguments)
elif terminal_data.new_window_arguments:
terminal_cmd.extend(terminal_data.new_window_arguments)
print(f'open-any-terminal: terminal is set to "{terminal}" {new_tab_text} {flatpak_text}')
if API_VERSION == ("4.0", "4.1"):
class OpenAnyTerminalShortcutProvider(GObject.GObject, FileManager.MenuProvider):
"""Provide keyboard shortcuts for opening terminals in Nautilus."""
def __init__(self):
super().__init__()
self.previous_cwd = expanduser("~")
gsettings_source = Gio.SettingsSchemaSource.get_default()
if gsettings_source.lookup(GSETTINGS_PATH, True):
self._gsettings = Gio.Settings.new(GSETTINGS_PATH)
self._setup_keybindings()
def get_background_items(self, current_folder: FileManager.FileInfo):
"""Update current URI when folder changes."""
if current_folder:
if current_folder.get_uri_scheme() in REMOTE_URI_SCHEME:
folder_path = current_folder.get_uri()
else:
folder_path = current_folder.get_location().get_path()
if folder_path and folder_path != self.previous_cwd:
self.previous_cwd = folder_path
return []
def _open_terminal(self, *_args):
"""Open the terminal at the specified URI."""
if self._gsettings.get_boolean(GSETTINGS_BIND_REMOTE):
open_remote_terminal_in_uri(self.previous_cwd)
else:
open_local_terminal_in_uri(self.previous_cwd)
def _setup_keybindings(self):
"""Set up custom keybindings for the extension."""
self.app = Gtk.Application.get_default()
if self.app is None:
print("No Gtk.Application found. Keybindings cannot be set.")
return
action = Gio.SimpleAction.new("open_any_terminal", None)
action.connect("activate", self._open_terminal)
self.app.add_action(action)
self._bind_shortcut()
self._gsettings.connect("changed", self._update_shortcut)
def _update_shortcut(self, _gsettings, key):
"""remove keybinding"""
if key == GSETTINGS_KEYBINDINGS:
self.app.set_accels_for_action("app.open_any_terminal", [])
self._bind_shortcut()
def _bind_shortcut(self):
"""Parse and update keybindings when settings change."""
shortcut = self._gsettings.get_string(GSETTINGS_KEYBINDINGS)
if not shortcut:
self.app.set_accels_for_action("app.open_any_terminal", [])
return
valid, key, mods = Gtk.accelerator_parse(shortcut)
if not valid:
print("Invalid shortcut in GSettings: %r", shortcut)
self.app.set_accels_for_action("app.open_any_terminal", [])
return
normalized = Gtk.accelerator_name(key, mods)
self.app.set_accels_for_action("app.open_any_terminal", [normalized])
elif API_VERSION in ("3.0", "2.0"):
class OpenAnyTerminalShortcutProviderLegacy(GObject.GObject, FileManager.LocationWidgetProvider):
"""Provide keyboard shortcuts for opening terminals in Nautilus/Caja."""
def __init__(self):
super().__init__()
gsettings_source = Gio.SettingsSchemaSource.get_default()
if gsettings_source.lookup(GSETTINGS_PATH, True):
self._gsettings = Gio.Settings.new(GSETTINGS_PATH)
self._gsettings.connect("changed", self._bind_shortcut)
self._create_accel_group()
self._window = None
self._uri = None
def _create_accel_group(self):
self._accel_group = Gtk.AccelGroup()
shortcut = self._gsettings.get_string(GSETTINGS_KEYBINDINGS)
key, mod = Gtk.accelerator_parse(shortcut)
self._accel_group.connect(key, mod, Gtk.AccelFlags.VISIBLE, self._open_terminal)
def _bind_shortcut(self, _gsettings, key):
if key == GSETTINGS_KEYBINDINGS:
self._accel_group.disconnect(self._open_terminal)
self._create_accel_group()
def _open_terminal(self, *_args):
if _gsettings.get_boolean(GSETTINGS_BIND_REMOTE):
open_local_terminal_in_uri(self._uri)
else:
open_remote_terminal_in_uri(self._uri)
def get_widget(self, uri, window):
"""follows uri and sets the correct window"""
self._uri = uri
if self._window:
self._window.remove_accel_group(self._accel_group)
if self._gsettings:
window.add_accel_group(self._accel_group)
self._window = window
class OpenAnyTerminalExtension(GObject.GObject, FileManager.MenuProvider):
"""Provide context menu items for opening terminals in Nautilus."""
def __init__(self):
super().__init__()
gsettings_source = Gio.SettingsSchemaSource.get_default()
if gsettings_source.lookup(GSETTINGS_PATH, True):
self._gsettings = Gio.Settings.new(GSETTINGS_PATH)
def _get_terminal_name(self):
if self._gsettings.get_boolean(GSETTINGS_USE_GENERIC_TERMINAL_NAME):
return _("Terminal")
return None
def _menu_dir_activate_cb(self, menu, file_, remote: bool):
if remote:
open_remote_terminal_in_uri(file_.get_uri())
else:
if file_.get_uri_scheme() == "smb":
file_uri = "file://" + quote(file_.get_location().get_path())
else:
file_uri = file_.get_uri()
open_local_terminal_in_uri(file_uri)
def _menu_exe_activate_cb(self, menu, file_, remote: bool):
if remote:
cmd = ssh_command_from_uri(file_.get_uri(), is_directory=False)
else:
result = urlparse(file_.get_uri())
file = unquote(result.path)
if result.scheme == "admin":
cmd = ["sudo", file]
elif terminal in ["xterm", "uxterm"]:
cmd = [f"exec {shlex.quote(file)}"]
else:
cmd = [file]
run_command_in_terminal(cmd)
def get_file_items(self, *args):
"""Generates a list of menu items for a file or folder in the Nautilus file manager."""
# `args` will be `[files: List[Nautilus.FileInfo]]` in Nautilus 4.0 API,
# and `[window: Gtk.Widget, files: List[Nautilus.FileInfo]]` in Nautilus 3.0 API.
files = args[-1]
if len(files) != 1:
return []
file_ = files[0]
if file_.is_directory():
return get_directory_menu_items(
file_, self._menu_dir_activate_cb, foreground=True, terminal_name=self._get_terminal_name()
)
if is_executable(file_.get_location()):
return get_executable_menu_items(file_, self._menu_exe_activate_cb, terminal_name=self._get_terminal_name())
return []
def get_background_items(self, *args):
"""Generates a list of background menu items for a file or folder in the Nautilus file manager."""
# `args` will be `[folder: Nautilus.FileInfo]` in Nautilus 4.0 API,
# and `[window: Gtk.Widget, file: Nautilus.FileInfo]` in Nautilus 3.0 API.
file_ = args[-1]
return get_directory_menu_items(
file_, self._menu_dir_activate_cb, foreground=False, terminal_name=self._get_terminal_name()
)
source = Gio.SettingsSchemaSource.get_default()
if source is not None and source.lookup(GSETTINGS_PATH, True):
_gsettings = Gio.Settings.new(GSETTINGS_PATH)
_gsettings.connect("changed", set_terminal_args)
set_terminal_args()
@@ -0,0 +1,57 @@
# Tokyo Night Day for btop, the light counterpart to tokyonight-moon.
#
# btop ships no Tokyo Night light variant at all, and the nearest stock light
# theme (flat-remix-light) is a different palette that happens to have a similar
# background. Same colours as kitty/themes/tokyonight-day.conf so the terminal
# and what runs inside it cannot disagree.
#
# Gradients keep the same low -> middle -> high meaning as the dark theme, using
# Day's darker, more saturated accents: the light versions of these hues are too
# faint to read as a filled meter.
theme[main_bg]="#e1e2e7"
theme[main_fg]="#3760bf"
theme[title]="#3760bf"
theme[hi_fg]="#2e7de9"
theme[selected_bg]="#c4c8da"
theme[selected_fg]="#2e7de9"
theme[inactive_fg]="#7079a8"
theme[proc_misc]="#587539"
theme[cpu_box]="#a8aecb"
theme[mem_box]="#a8aecb"
theme[net_box]="#a8aecb"
theme[proc_box]="#a8aecb"
theme[div_line]="#c4c8da"
theme[temp_start]="#2e7de9"
theme[temp_mid]="#8c6c3e"
theme[temp_end]="#f52a65"
theme[cpu_start]="#2e7de9"
theme[cpu_mid]="#9854f1"
theme[cpu_end]="#f52a65"
theme[free_start]="#c4c8da"
theme[free_mid]="#007197"
theme[free_end]="#2e7de9"
theme[cached_start]="#007197"
theme[cached_mid]="#2e7de9"
theme[cached_end]="#9854f1"
theme[available_start]="#8c6c3e"
theme[available_mid]="#b15c00"
theme[available_end]="#f52a65"
theme[used_start]="#587539"
theme[used_mid]="#8c6c3e"
theme[used_end]="#f52a65"
theme[download_start]="#c4c8da"
theme[download_mid]="#2e7de9"
theme[download_end]="#007197"
theme[upload_start]="#c4c8da"
theme[upload_mid]="#9854f1"
theme[upload_end]="#7847bd"
@@ -0,0 +1,56 @@
# Tokyo Night Moon for btop, matched to Panama's palette.
#
# btop ships a "tokyo-night" theme, but it is the Night variant (#1a1b26). The
# rest of this desktop is Moon (#222436), and two Tokyo Nights side by side read
# as a mistake rather than as a choice.
#
# The *_start/_mid/_end triples are gradients btop draws meters with: low,
# middle, and high. They run blue -> yellow -> red so a saturated resource is
# obvious at a glance without reading the number.
theme[main_bg]="#222436"
theme[main_fg]="#c8d3f5"
theme[title]="#c8d3f5"
theme[hi_fg]="#82aaff"
theme[selected_bg]="#3b4261"
theme[selected_fg]="#82aaff"
theme[inactive_fg]="#636da6"
theme[proc_misc]="#a5e8b5"
theme[cpu_box]="#4d5685"
theme[mem_box]="#4d5685"
theme[net_box]="#4d5685"
theme[proc_box]="#4d5685"
theme[div_line]="#3b4261"
theme[temp_start]="#82aaff"
theme[temp_mid]="#ffc777"
theme[temp_end]="#ff757f"
theme[cpu_start]="#82aaff"
theme[cpu_mid]="#c099ff"
theme[cpu_end]="#ff757f"
theme[free_start]="#3b4261"
theme[free_mid]="#589ed7"
theme[free_end]="#86e1fc"
theme[cached_start]="#86e1fc"
theme[cached_mid]="#82aaff"
theme[cached_end]="#c099ff"
theme[available_start]="#ffc777"
theme[available_mid]="#ff966c"
theme[available_end]="#ff757f"
theme[used_start]="#a5e8b5"
theme[used_mid]="#ffc777"
theme[used_end]="#ff757f"
theme[download_start]="#3b4261"
theme[download_mid]="#82aaff"
theme[download_end]="#86e1fc"
theme[upload_start]="#3b4261"
theme[upload_mid]="#c099ff"
theme[upload_end]="#fca7ea"
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
source ~/.local/share/sunhat/defaults/bash/api_keys
printf '%s\t%s' \
"$(bw --session "$BW_SESSION" get username "$1")" \
"$(bw --session "$BW_SESSION" get password "$1")"
+40
View File
@@ -0,0 +1,40 @@
# espanso configuration file
# For a complete introduction, visit the official docs at: https://espanso.org/docs/
# You can use this file to define the global configuration options for espanso.
# These are the parameters that will be used by default on every application,
# but you can also override them on a per-application basis.
# To make customization easier, this file contains some of the commonly used
# parameters. Feel free to uncomment and tune them to fit your needs!
# --- Toggle key
# Customize the key used to disable and enable espanso (when double tapped)
# Available options: CTRL, SHIFT, ALT, CMD, OFF
# You can also specify the key variant, such as LEFT_CTRL, RIGHT_SHIFT, etc...
# toggle_key: ALT
# You can also disable the toggle key completely with
# toggle_key: OFF
# --- Injection Backend
# Espanso supports multiple ways of injecting text into applications. Each of
# them has its quirks, therefore you may want to change it if you are having problems.
# By default, espanso uses the "Auto" backend which should work well in most cases,
# but you may want to try the "Clipboard" or "Inject" backend in case of issues.
# backend: Clipboard
# --- Auto-restart
# Enable/disable the config auto-reload after a file change is detected.
# auto_restart: false
# --- Clipboard threshold
# Because injecting long texts char-by-char is a slow operation, espanso automatically
# uses the clipboard if the text is longer than 'clipboard_threshold' characters.
clipboard_threshold: 500
# For a list of all the available options, visit the official docs at: https://espanso.org/docs/
show_icon: false
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,56 @@
# html-utils-package
Make HTML5 easier and less time-consuming with this [Espanso](https://espanso.org/) package!
# Installation
Make sure you have already installed [Espanso](https://espanso.org/install/) first.
```
espanso install html-utils-package
```
That's all. You can start using the package. Open your favorite editor and type `::docskel` to test!
# Preview
You can choose between all of them from the Search-bar:
![Search-bar](images/search-bar.png)
# Triggers
Here you can see some of them:
| Trigger | Result |
| ------------- | ------------- |
| `::docskel` | Generates an empty document with `utf-8` and `viewport` headers (unindented) |
| `::doctype` | `<!DOCTYPE html>` |
| `::meta-charset` | `<meta charset="">` |
| `::meta-utf-8` | `<meta charset="UTF-8">` |
| `::meta-viewport` | `<meta name="viewport" content="width=device-width, initial-scale=1">` |
| `::meta-author` | `<meta name="author" content="">` |
| `::meta-desc` | `<meta name="description" content="">` |
| `::meta-keywords` | `<meta name="keywords" content="">` |
| `::title` | `<title></title>` |
| `::div` | `<div></div>` |
| `::html` | `<html></html>` |
| `::head` | `<head></head>` |
| `::body` | `<body></body>` |
| `::a` | `<a href=""></a>` |
| `::br` | `<br>` |
| `::button` | `<button type="button"></button> ` |
| `::style` | `<style></style>` |
| `::css` | `<link rel="stylesheet" type="text/css" href="">` |
| `::script` | `<script></script>` |
| `::js` | `<script type="text/javascript" src=""></script>` |
| `::form` | `<form action="" method=""></form>` |
| `::label` | `<label for=""></label>` |
| `::input-submit` | `<input type="submit" value="">` |
| `::input-text` | `<input type="text" name="" id="">` |
| `::input-password` | `<input type="password" name="" id="">` |
| `::input-radio` | `<input type="radio" name="" id="" value="">` |
| `::input-checkbox` | `<input type="checkbox" name="" id="" value="">` |
| `::input-file` | `<input type="file" name="" id="">` |
# Contributions
If you feel like there's any important tag/snippet missing, feel free to create a Pull Request or open an [Issue](https://github.com/woodenbell/html-utils-package/issues/new).
@@ -0,0 +1,7 @@
author: Gabriel Barbosa
description: A simple package to make coding in HTML5 easier.
name: html-utils-package
title: HTML utilities package
version: 2.0.1
homepage: "https://github.com/woodenbell/html-utils-package"
tags: ["frontend", "html", "development"]
@@ -0,0 +1,2 @@
---
hub
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

@@ -0,0 +1,239 @@
matches:
- trigger: "::doctype"
label: "HTML - doctype"
replace: >-
<!DOCTYPE html>
- trigger: "::meta-charset"
label: "HTML - meta-charset"
replace: >-
<meta charset="$|$">
- trigger: "::meta-utf-8"
label: "HTML - meta-utf-8"
replace: >-
<meta charset="UTF-8">
- trigger: "::meta-viewport"
label: "HTML - meta-viewport"
replace: >-
<meta name="viewport" content="width=device-width, initial-scale=1">
- trigger: "::meta-author"
label: "HTML - meta-autor"
replace: >-
<meta name="author" content="$|$">
- trigger: "::meta-desc"
label: "HTML - meta-desc"
replace: >-
<meta name="description" content="$|$">
- trigger: "::meta-keywords"
label: "HTML - meta-keywords"
replace: >-
<meta name="keywords" content="$|$">
- trigger: "::title"
label: "HTML - title"
replace: >-
<title>$|$</title>
- trigger: "::div"
label: "HTML - div"
replace: >-
<div>$|$</div>
- trigger: "::html"
label: "HTML - html"
replace: >-
<html>$|$</html>
- trigger: "::head"
label: "HTML - head"
replace: >-
<head>$|$</head>
- trigger: "::body"
label: "HTML - body"
replace: >-
<body>$|$</body>
- trigger: "::inline-css"
label: "HTML - inline-css"
replace: style="{{element}}:$|$;"
vars:
- name: element
type: choice
params:
values:
- "color"
- "background-color"
- "padding"
- "font-family"
- "font-size"
- "font-weight"
- "border"
- "padding"
- "margin"
- trigger: "::a"
label: "HTML - a"
replace: >-
<a href="$|$"></a>
- trigger: "::2a"
label: "HTML - 2a"
replace: <a href="{{clipboard}}" target="_blank" rel="noopener noreferrer">$|$</a>
vars:
- name: "clipboard"
type: "clipboard"
- trigger: "::br"
label: "HTML - br"
replace: >-
<br>
- trigger: "::p"
label: "HTML - p"
replace: >-
<p>$|$</p>
- trigger: "::block"
label: "HTML - block"
replace: >-
<blockquote>$|$</blockquote>
- trigger: "::button"
label: "HTML - button"
replace: >-
<button type="button">$|$</button>
- trigger: "::style"
label: "HTML - style"
replace: >-
<style>$|$</style>
- trigger: "::css"
label: "HTML - css"
replace: >-
<link rel="stylesheet" type="text/css" href="$|$">
- trigger: "::ul"
label: "HTML - ul"
replace: |
<ul>
<li>$|$</li>
</ul>
- trigger: "::li"
label: "HTML - li"
replace: >-
<li>$|$</li>
- trigger: "::table"
label: "HTML - table"
replace: |
<table width="$|$" border="" align="">
<tr>
<td width=""></td>
</tr>
</table>
- trigger: "::td"
label: "HTML - td"
replace: >-
<td>$|$</td>
- trigger: "::select"
label: "HTML - select"
replace: |
<select name="$|$" id="">
<option value=""></option>
</select>
- trigger: "::optgroup"
label: "HTML - optgroup"
replace: |
<select name="$|$" id="">
<optgroup label="">
<option value=""></option>
</optgroup>
</select>
- trigger: "::option"
label: "HTML - option"
replace: >-
<option value="$|$"></option>
- trigger: "::script"
label: "HTML - script"
replace: >-
<script>$|$</script>
- trigger: "::js"
label: "HTML - js"
replace: >-
<script type="text/javascript" src="$|$"></script>
- trigger: "::form"
label: "HTML - form"
replace: >-
<form action="$|$" method=""></form>
- trigger: "::label"
label: "HTML - label"
replace: >-
<label for="$|$"></label>
- trigger: "::img"
label: "HTML - img"
replace: >-
<img src="$|$" alt="">
- trigger: "::input-submit"
label: "HTML - input-submit"
replace: >-
<input type="submit" value="$|$">
- trigger: "::input-text"
label: "HTML - input-text"
replace: >-
<input type="text" name="$|$" id="">
- trigger: "::input-password"
label: "HTML - input-password"
replace: >-
<input type="password" name="$|$" id="">
- trigger: "::input-radio"
label: "HTML - input-radio"
replace: >-
<input type="radio" name="$|$" id="" value="">
- trigger: "::input-checkbox"
label: "HTML - input-checkbox"
replace: >-
<input type="checkbox" name="$|$" id="" value="">
- trigger: "::input-file"
label: "HTML - input-file"
replace: >-
<input type="file" name="$|$" id="">
- trigger: "::docskel"
label: "HTML - docskel"
replace: |
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>$|$</title>
</head>
<body>
</body>
</html>
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 José Ferreira
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,23 @@
# Available matches
| Trigger | Replace |
|--------------|-----------------------------------------------------------------|
| :block: | \```🖰<br>``` |
| :code: | \`🖰` |
| :h1: | # |
| :h2: | ## |
| :h3: | ### |
| :h4: | #### |
| :h5: | ##### |
| :h6: | ###### |
| :bold: | \*\*🖰** |
| :italic: | \*🖰\* |
| :strike: | \~\~🖰~~ |
| :url: | \[🖰]() |
| :image: | !\[](🖰) |
| :horizontal: | ___<br> |
| :task: | - [ ] |
| :donetask: | - [x] |
| :collapse: | \<details><br>\<summary>\</summary><br>🖱<br>\</details> |
| :comment: | \<\!--- 🖱 --> |
**Note**: The symbol "🖰" denotes where your mouse cursor will be after the trigger.
@@ -0,0 +1,7 @@
author: "Jos\xE9 Ferreira"
description: A simple package to make writing Markdown easier
name: markdown-shortcuts
title: Markdown shortcuts
version: 0.2.0
homepage: "https://github.com/jpmvferreira/espanso-mega-pack"
tags: ["markdown", "development", "writing"]
@@ -0,0 +1,2 @@
---
hub
@@ -0,0 +1,83 @@
matches:
- trigger: ":block:"
label: "Markdown - Code block"
replace: |-
```$|$
```
force_clipboard: true
- trigger: ":code:"
label: "Markdown - Inline code"
replace: |-
`$|$`
force_clipboard: true
- trigger: ":h1:"
label: "Markdown - Header1"
replace: "#"
- trigger: ":h2:"
label: "Markdown - Header2"
replace: "##"
- trigger: ":h3:"
label: "Markdown - Header3"
replace: "###"
- trigger: ":h4:"
label: "Markdown - Header4"
replace: "####"
- trigger: ":h5:"
label: "Markdown - Header5"
replace: "#####"
- trigger: ":h6:"
label: "Markdown - Header6"
replace: "######"
- trigger: ":bold:"
label: "Markdown - Bold"
replace: "**$|$**"
- trigger: ":italic:"
label: "Markdown - Italic"
replace: "*$|$*"
- trigger: ":strike:"
label: "Markdown - Strikethrough"
replace: "~~$|$~~"
- trigger: ":url:"
label: "Markdown - URL"
replace: "[$|$]()"
- trigger: ":img:"
label: "Markdown - Image"
replace: "![]($|$)"
- trigger: ":horizontal:"
label: "Markdown - Horizontal line"
replace: "___"
- trigger: ":task:"
label: "Markdown - Task"
replace: "- [ ] "
- trigger: ":taskdone:"
label: "Markdown - Task completed"
replace: "- [x] "
- trigger: ":collapse:"
label: "Markdown - Collapse block"
replace: |-
<details>
<summary></summary>
$|$
</details>
force_clipboard: true
- trigger: ":comment:"
label: "Markdown - Comment"
replace: "<!--- $|$ -->"
force_clipboard: true
@@ -0,0 +1,27 @@
Copyright (c) 2019 Timo Runge <[email protected]>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,22 @@
# misspell-en
misspell-en is a espanso package which is replacing commonly misspelled english words.
The package is based on [github.com/client9/misspell](https://github.com/client9/misspell).
## Installation
Install the package with:
```
espanso install misspell-en
```
## Usage
Type `yuo` and see what's happening.
## License
[BSD 3-Clause "New" or "Revised" License](LICENSE)
Misspell is [MIT](https://github.com/client9/misspell/blob/master/LICENSE).
@@ -0,0 +1,7 @@
author: Timo Runge
description: Replace commonly misspelled english words.
name: misspell-en
title: Misspell EN
version: 0.1.2
homepage: "https://github.com/timorunge/espanso-misspell-en"
tags: ["spell-correction", "english"]
@@ -0,0 +1,2 @@
---
hub
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,72 @@
{
"overrides": [
{
"wmClass": "jetbrains-toolbox",
"mode": "float"
},
{
"wmClass": "Com.github.amezin.ddterm",
"mode": "float"
},
{
"wmClass": "Com.github.donadigo.eddy",
"mode": "float"
},
{
"wmClass": "Conky",
"mode": "float"
},
{
"wmClass": "Gnome-initial-setup",
"mode": "float"
},
{
"wmClass": "org.gnome.Calculator",
"mode": "float"
},
{
"wmClass": "gnome-terminal-preferences",
"mode": "float"
},
{
"wmClass": "Guake",
"mode": "float"
},
{
"wmClass": "zoom",
"mode": "float"
},
{
"wmClass": "mpv",
"mode": "float"
},
{
"wmClass": "Bitwarden",
"mode": "float"
},
{
"wmClass": "Hidamari",
"mode": "float"
},
{
"wmClass": "com.mattjakeman.ExtensionManager",
"mode": "float"
},
{
"wmClass": "Cider",
"mode": "float"
},
{
"wmClass": "Ulauncher",
"mode": "float"
},
{
"wmClass": "com.nextcloud.desktopclient.nextcloud",
"mode": "float"
},
{
"wmClass": "Emulator",
"mode": "float"
}
]
}
@@ -0,0 +1,72 @@
{
"overrides": [
{
"wmClass": "jetbrains-toolbox",
"mode": "float"
},
{
"wmClass": "Com.github.amezin.ddterm",
"mode": "float"
},
{
"wmClass": "Com.github.donadigo.eddy",
"mode": "float"
},
{
"wmClass": "Conky",
"mode": "float"
},
{
"wmClass": "Gnome-initial-setup",
"mode": "float"
},
{
"wmClass": "org.gnome.Calculator",
"mode": "float"
},
{
"wmClass": "gnome-terminal-preferences",
"mode": "float"
},
{
"wmClass": "Guake",
"mode": "float"
},
{
"wmClass": "zoom",
"mode": "float"
},
{
"wmClass": "mpv",
"mode": "float"
},
{
"wmClass": "Bitwarden",
"mode": "float"
},
{
"wmClass": "Hidamari",
"mode": "float"
},
{
"wmClass": "com.mattjakeman.ExtensionManager",
"mode": "float"
},
{
"wmClass": "Cider",
"mode": "float"
},
{
"wmClass": "Ulauncher",
"mode": "float"
},
{
"wmClass": "com.nextcloud.desktopclient.nextcloud",
"mode": "float"
},
{
"wmClass": "Emulator",
"mode": "float"
}
]
}
@@ -0,0 +1,72 @@
{
"overrides": [
{
"wmClass": "jetbrains-toolbox",
"mode": "float"
},
{
"wmClass": "Com.github.amezin.ddterm",
"mode": "float"
},
{
"wmClass": "Com.github.donadigo.eddy",
"mode": "float"
},
{
"wmClass": "Conky",
"mode": "float"
},
{
"wmClass": "Gnome-initial-setup",
"mode": "float"
},
{
"wmClass": "org.gnome.Calculator",
"mode": "float"
},
{
"wmClass": "gnome-terminal-preferences",
"mode": "float"
},
{
"wmClass": "Guake",
"mode": "float"
},
{
"wmClass": "zoom",
"mode": "float"
},
{
"wmClass": "mpv",
"mode": "float"
},
{
"wmClass": "Bitwarden",
"mode": "float"
},
{
"wmClass": "Hidamari",
"mode": "float"
},
{
"wmClass": "com.mattjakeman.ExtensionManager",
"mode": "float"
},
{
"wmClass": "Cider",
"mode": "float"
},
{
"wmClass": "Ulauncher",
"mode": "float"
},
{
"wmClass": "com.nextcloud.desktopclient.nextcloud",
"mode": "float"
},
{
"wmClass": "Emulator",
"mode": "float"
}
]
}
@@ -0,0 +1,72 @@
{
"overrides": [
{
"wmClass": "jetbrains-toolbox",
"mode": "float"
},
{
"wmClass": "Com.github.amezin.ddterm",
"mode": "float"
},
{
"wmClass": "Com.github.donadigo.eddy",
"mode": "float"
},
{
"wmClass": "Conky",
"mode": "float"
},
{
"wmClass": "Gnome-initial-setup",
"mode": "float"
},
{
"wmClass": "org.gnome.Calculator",
"mode": "float"
},
{
"wmClass": "gnome-terminal-preferences",
"mode": "float"
},
{
"wmClass": "Guake",
"mode": "float"
},
{
"wmClass": "zoom",
"mode": "float"
},
{
"wmClass": "mpv",
"mode": "float"
},
{
"wmClass": "Bitwarden",
"mode": "float"
},
{
"wmClass": "Hidamari",
"mode": "float"
},
{
"wmClass": "com.mattjakeman.ExtensionManager",
"mode": "float"
},
{
"wmClass": "Cider",
"mode": "float"
},
{
"wmClass": "Ulauncher",
"mode": "float"
},
{
"wmClass": "com.nextcloud.desktopclient.nextcloud",
"mode": "float"
},
{
"wmClass": "Emulator",
"mode": "float"
}
]
}
@@ -0,0 +1,72 @@
{
"overrides": [
{
"wmClass": "jetbrains-toolbox",
"mode": "float"
},
{
"wmClass": "Com.github.amezin.ddterm",
"mode": "float"
},
{
"wmClass": "Com.github.donadigo.eddy",
"mode": "float"
},
{
"wmClass": "Conky",
"mode": "float"
},
{
"wmClass": "Gnome-initial-setup",
"mode": "float"
},
{
"wmClass": "org.gnome.Calculator",
"mode": "float"
},
{
"wmClass": "gnome-terminal-preferences",
"mode": "float"
},
{
"wmClass": "Guake",
"mode": "float"
},
{
"wmClass": "zoom",
"mode": "float"
},
{
"wmClass": "mpv",
"mode": "float"
},
{
"wmClass": "Bitwarden",
"mode": "float"
},
{
"wmClass": "Hidamari",
"mode": "float"
},
{
"wmClass": "com.mattjakeman.ExtensionManager",
"mode": "float"
},
{
"wmClass": "Cider",
"mode": "float"
},
{
"wmClass": "Ulauncher",
"mode": "float"
},
{
"wmClass": "com.nextcloud.desktopclient.nextcloud",
"mode": "float"
},
{
"wmClass": "Emulator",
"mode": "float"
}
]
}
@@ -0,0 +1,72 @@
{
"overrides": [
{
"wmClass": "jetbrains-toolbox",
"mode": "float"
},
{
"wmClass": "Com.github.amezin.ddterm",
"mode": "float"
},
{
"wmClass": "Com.github.donadigo.eddy",
"mode": "float"
},
{
"wmClass": "Conky",
"mode": "float"
},
{
"wmClass": "Gnome-initial-setup",
"mode": "float"
},
{
"wmClass": "org.gnome.Calculator",
"mode": "float"
},
{
"wmClass": "gnome-terminal-preferences",
"mode": "float"
},
{
"wmClass": "Guake",
"mode": "float"
},
{
"wmClass": "zoom",
"mode": "float"
},
{
"wmClass": "mpv",
"mode": "float"
},
{
"wmClass": "Bitwarden",
"mode": "float"
},
{
"wmClass": "Hidamari",
"mode": "float"
},
{
"wmClass": "com.mattjakeman.ExtensionManager",
"mode": "float"
},
{
"wmClass": "Cider",
"mode": "float"
},
{
"wmClass": "Ulauncher",
"mode": "float"
},
{
"wmClass": "com.nextcloud.desktopclient.nextcloud",
"mode": "float"
},
{
"wmClass": "Emulator",
"mode": "float"
}
]
}
+76
View File
@@ -0,0 +1,76 @@
{
"overrides": [
{
"wmClass": "jetbrains-toolbox",
"mode": "float"
},
{
"wmClass": "Com.github.amezin.ddterm",
"mode": "float"
},
{
"wmClass": "Com.github.donadigo.eddy",
"mode": "float"
},
{
"wmClass": "Conky",
"mode": "float"
},
{
"wmClass": "Gnome-initial-setup",
"mode": "float"
},
{
"wmClass": "org.gnome.Calculator",
"mode": "float"
},
{
"wmClass": "gnome-terminal-preferences",
"mode": "float"
},
{
"wmClass": "Guake",
"mode": "float"
},
{
"wmClass": "zoom",
"mode": "float"
},
{
"wmClass": "Bitwarden",
"mode": "float"
},
{
"wmClass": "Hidamari",
"mode": "float"
},
{
"wmClass": "com.mattjakeman.ExtensionManager",
"mode": "float"
},
{
"wmClass": "Cider",
"mode": "float"
},
{
"wmClass": "Ulauncher",
"mode": "float"
},
{
"wmClass": "com.nextcloud.desktopclient.nextcloud",
"mode": "float"
},
{
"wmClass": "mpv",
"mode": "float"
},
{
"wmClass": "Spotify",
"mode": "float"
},
{
"wmClass": "bitwarden",
"mode": "float"
}
]
}
@@ -0,0 +1,190 @@
.tiled {
color: rgba(236, 94, 94, 1);
opacity: 1;
border-width: 3px;
}
.split {
color: rgba(255, 246, 108, 1);
opacity: 1;
border-width: 3px;
}
.stacked {
color: rgba(247, 162, 43, 1);
opacity: 1;
border-width: 3px;
}
.tabbed {
color: rgba(17, 199, 224, 1);
opacity: 1;
border-width: 3px;
}
.floated {
color: rgba(180, 167, 214, 1);
border-width: 3px;
opacity: 1;
}
.window-tiled-border {
border-width: 3px;
border-color: rgb(98,160,234);
border-style: solid;
border-radius: 14px;
}
.window-split-border {
border-width: 3px;
border-color: rgb(98,160,234);
border-style: solid;
border-radius: 14px;
}
/* forge-36le: St does not paint an asymmetric (single-side) border when a
* border-radius is set, so the directional hint never rendered. Drop the radius
* on the one-edge variants; St paints a straight single-side border reliably. */
.window-split-horizontal {
border-left-width: 0;
border-top-width: 0;
border-bottom-width: 0;
border-radius: 0;
}
.window-split-vertical {
border-left-width: 0;
border-top-width: 0;
border-right-width: 0;
border-radius: 0;
}
.window-stacked-border {
border-width: 3px;
border-color: rgb(98,160,234);
border-style: solid;
border-radius: 14px;
}
.window-tabbed-border {
border-width: 3px;
border-color: rgb(98,160,234);
border-style: solid;
border-radius: 14px;
}
.window-tabbed-bg {
border-radius: 8px;
}
.window-tabbed-tab {
background-color: rgba(54, 47, 45, 1);
border-color: rgba(98,160,234,0.6);
border-width: 1px;
border-radius: 8px;
color: white;
margin: 1px;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
}
.window-tabbed-tab-active {
background-color: rgb(98,160,234);
color: black;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
}
.window-tabbed-tab-close {
padding: 3px;
margin: 4px;
border-radius: 16px;
width: 16px;
background-color: #e06666;
}
.window-tabbed-tab-icon {
margin: 3px;
}
.window-floated-border {
border-width: 3px;
border-color: rgb(98,160,234);
border-style: solid;
border-radius: 14px;
}
.window-tilepreview-tiled {
border-width: 1px;
border-color: rgba(98,160,234,0.3);
border-style: solid;
border-radius: 14px;
background-color: rgba(98,160,234,0.2);
}
.window-tilepreview-stacked {
border-width: 1px;
border-color: rgba(98,160,234,0.3);
border-style: solid;
border-radius: 14px;
background-color: rgba(98,160,234,0.2);
}
.window-tilepreview-swap {
border-width: 1px;
border-color: rgba(162, 247, 43, 0.4);
border-style: solid;
border-radius: 14px;
background-color: rgba(162, 247, 43, 0.4);
}
.window-tilepreview-tabbed {
border-width: 1px;
border-color: rgba(98,160,234,0.3);
border-style: solid;
border-radius: 14px;
background-color: rgba(98,160,234,0.2);
}
/* Keybindings Cheatsheet Overlay */
.forge-cheatsheet {
background-color: #2d2d2d;
border-radius: 16px;
padding: 24px 32px;
border: 1px solid #4d4d4d;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}
.forge-cheatsheet-title {
font-size: 16px;
font-weight: bold;
color: #ffffff;
margin-bottom: 20px;
text-align: center;
}
.forge-cheatsheet-category {
font-size: 13px;
font-weight: bold;
color: #78aeed;
margin-top: 16px;
margin-bottom: 8px;
}
.forge-cheatsheet-row {
font-size: 12px;
color: #eeeeec;
margin: 4px 0;
padding: 2px 0;
}
.forge-cheatsheet-key {
font-family: monospace;
font-weight: bold;
color: #f5c211;
min-width: 180px;
}
.forge-cheatsheet-desc {
color: #d0cfcc;
}
@@ -0,0 +1,190 @@
.tiled {
color: rgba(236, 94, 94, 1);
opacity: 1;
border-width: 3px;
}
.split {
color: rgba(255, 246, 108, 1);
opacity: 1;
border-width: 3px;
}
.stacked {
color: rgba(247, 162, 43, 1);
opacity: 1;
border-width: 3px;
}
.tabbed {
color: rgba(17, 199, 224, 1);
opacity: 1;
border-width: 3px;
}
.floated {
color: rgba(180, 167, 214, 1);
border-width: 3px;
opacity: 1;
}
.window-tiled-border {
border-width: 3px;
border-color: rgb(98,160,234);
border-style: solid;
border-radius: 14px;
}
.window-split-border {
border-width: 3px;
border-color: rgb(98,160,234);
border-style: solid;
border-radius: 14px;
}
/* forge-36le: St does not paint an asymmetric (single-side) border when a
* border-radius is set, so the directional hint never rendered. Drop the radius
* on the one-edge variants; St paints a straight single-side border reliably. */
.window-split-horizontal {
border-left-width: 0;
border-top-width: 0;
border-bottom-width: 0;
border-radius: 0;
}
.window-split-vertical {
border-left-width: 0;
border-top-width: 0;
border-right-width: 0;
border-radius: 0;
}
.window-stacked-border {
border-width: 3px;
border-color: rgb(98,160,234);
border-style: solid;
border-radius: 14px;
}
.window-tabbed-border {
border-width: 3px;
border-color: rgb(98,160,234);
border-style: solid;
border-radius: 14px;
}
.window-tabbed-bg {
border-radius: 8px;
}
.window-tabbed-tab {
background-color: rgba(54, 47, 45, 1);
border-color: rgba(98,160,234,0.6);
border-width: 1px;
border-radius: 8px;
color: white;
margin: 1px;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
}
.window-tabbed-tab-active {
background-color: rgb(98,160,234);
color: black;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
}
.window-tabbed-tab-close {
padding: 3px;
margin: 4px;
border-radius: 16px;
width: 16px;
background-color: #e06666;
}
.window-tabbed-tab-icon {
margin: 3px;
}
.window-floated-border {
border-width: 1px;
border-color: rgb(98,160,234);
border-style: solid;
border-radius: 14px;
}
.window-tilepreview-tiled {
border-width: 1px;
border-color: rgba(98,160,234,0.3);
border-style: solid;
border-radius: 14px;
background-color: rgba(98,160,234,0.2);
}
.window-tilepreview-stacked {
border-width: 1px;
border-color: rgba(98,160,234,0.3);
border-style: solid;
border-radius: 14px;
background-color: rgba(98,160,234,0.2);
}
.window-tilepreview-swap {
border-width: 1px;
border-color: rgba(162, 247, 43, 0.4);
border-style: solid;
border-radius: 14px;
background-color: rgba(162, 247, 43, 0.4);
}
.window-tilepreview-tabbed {
border-width: 1px;
border-color: rgba(98,160,234,0.3);
border-style: solid;
border-radius: 14px;
background-color: rgba(98,160,234,0.2);
}
/* Keybindings Cheatsheet Overlay */
.forge-cheatsheet {
background-color: #2d2d2d;
border-radius: 16px;
padding: 24px 32px;
border: 1px solid #4d4d4d;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}
.forge-cheatsheet-title {
font-size: 16px;
font-weight: bold;
color: #ffffff;
margin-bottom: 20px;
text-align: center;
}
.forge-cheatsheet-category {
font-size: 13px;
font-weight: bold;
color: #78aeed;
margin-top: 16px;
margin-bottom: 8px;
}
.forge-cheatsheet-row {
font-size: 12px;
color: #eeeeec;
margin: 4px 0;
padding: 2px 0;
}
.forge-cheatsheet-key {
font-family: monospace;
font-weight: bold;
color: #f5c211;
min-width: 180px;
}
.forge-cheatsheet-desc {
color: #d0cfcc;
}
+32
View File
@@ -0,0 +1,32 @@
# Theme
theme = TokyoNight Moon
font-family = "VictorMono Nerd Font Mono"
#font-family-bold = "VictorMono Nerd Font Mono, Bold"
#font-family-italic = "VictorMono Nerd Font Mono, Regular Italic"
#font-family-bold-italic = "VictorMono Nerd Font Mono, Bold Italic"
font-size = 15
background-opacity = 0.92
background-blur = true
shell-integration = detect
# Keybindings
keybind = ctrl+shift+r=reload_config
## Tabs
keybind = ctrl+shift+h=previous_tab
keybind = ctrl+shift+l=next_tab
keybind = ctrl+shift+j=move_tab:-1
keybind = ctrl+shift+k=move_tab:1
keybind = ctrl+shift+q=close_tab
## Splits
keybind = ctrl+shift+b=new_split:right
keybind = ctrl+shift+d=new_split:down
keybind = ctrl+shift+,=goto_split:previous
keybind = ctrl+shift+.=goto_split:next
keybind = ctrl+shift+up=goto_split:up
keybind = ctrl+shift+down=goto_split:down
keybind = ctrl+shift+left=goto_split:left
keybind = ctrl+shift+right=goto_split:right
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#262427
palette = 1=#ff666d
palette = 2=#b3e03a
palette = 3=#ffc739
palette = 4=#00cde8
palette = 5=#a392e8
palette = 6=#9deaf6
palette = 7=#fcfcfa
palette = 8=#545452
palette = 9=#ff7e83
palette = 10=#bee55e
palette = 11=#ffd05e
palette = 12=#1bd5eb
palette = 13=#b0a3eb
palette = 14=#acedf8
palette = 15=#fcfcfa
background = #262427
foreground = #fcfcfa
cursor-color = #fcfcfa
cursor-text = #000000
selection-background = #fcfcfa
selection-foreground = #262427
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#a03050
palette = 2=#40d080
palette = 3=#e09040
palette = 4=#3060b0
palette = 5=#603090
palette = 6=#0090c0
palette = 7=#dbded8
palette = 8=#685656
palette = 9=#c06060
palette = 10=#90d050
palette = 11=#e0d000
palette = 12=#00b0c0
palette = 13=#801070
palette = 14=#20b0c0
palette = 15=#ffffff
background = #040404
foreground = #feffff
cursor-color = #e0d000
cursor-text = #000000
selection-background = #606060
selection-foreground = #ffffff
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#090300
palette = 1=#db2d20
palette = 2=#01a252
palette = 3=#caba00
palette = 4=#01a0e4
palette = 5=#a16a94
palette = 6=#8fbece
palette = 7=#a5a2a2
palette = 8=#5c5855
palette = 9=#dbaec3
palette = 10=#3a3432
palette = 11=#4a4543
palette = 12=#807d7c
palette = 13=#bcbbba
palette = 14=#cdab53
palette = 15=#f7f7f7
background = #f7f7f7
foreground = #4a4543
cursor-color = #4a4543
cursor-text = #f7f7f7
selection-background = #a5a2a2
selection-foreground = #4a4543
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#090300
palette = 1=#db2d20
palette = 2=#01a252
palette = 3=#fded02
palette = 4=#01a0e4
palette = 5=#a16a94
palette = 6=#b5e4f4
palette = 7=#a5a2a2
palette = 8=#5c5855
palette = 9=#e8bbd0
palette = 10=#47413f
palette = 11=#4a4543
palette = 12=#807d7c
palette = 13=#d6d5d4
palette = 14=#cdab53
palette = 15=#f7f7f7
background = #090300
foreground = #a5a2a2
cursor-color = #a5a2a2
cursor-text = #090300
selection-background = #4a4543
selection-foreground = #a5a2a2
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#191919
palette = 1=#aa342e
palette = 2=#4b8c0f
palette = 3=#dbba00
palette = 4=#1370d3
palette = 5=#c43ac3
palette = 6=#008eb0
palette = 7=#bebebe
palette = 8=#525252
palette = 9=#f05b50
palette = 10=#95dc55
palette = 11=#ffe763
palette = 12=#60a4ec
palette = 13=#e26be2
palette = 14=#60b6cb
palette = 15=#f7f7f7
background = #102040
foreground = #dddddd
cursor-color = #007acc
cursor-text = #bfdbfe
selection-background = #bfdbfe
selection-foreground = #000000
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#cd0000
palette = 2=#00cd00
palette = 3=#cdcd00
palette = 4=#1093f5
palette = 5=#cd00cd
palette = 6=#00cdcd
palette = 7=#faebd7
palette = 8=#404040
palette = 9=#ff0000
palette = 10=#00ff00
palette = 11=#ffff00
palette = 12=#11b5f6
palette = 13=#ff00ff
palette = 14=#00ffff
palette = 15=#ffffff
background = #111416
foreground = #eeeeec
cursor-color = #bbbbbb
cursor-text = #ffffff
selection-background = #eeeeec
selection-foreground = #333333
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#040404
palette = 1=#d84a33
palette = 2=#5da602
palette = 3=#eebb6e
palette = 4=#417ab3
palette = 5=#e5c499
palette = 6=#bdcfe5
palette = 7=#dbded8
palette = 8=#685656
palette = 9=#d76b42
palette = 10=#99b52c
palette = 11=#ffb670
palette = 12=#97d7ef
palette = 13=#aa7900
palette = 14=#bdcfe5
palette = 15=#e4d5c7
background = #040404
foreground = #feffff
cursor-color = #feffff
cursor-text = #000000
selection-background = #606060
selection-foreground = #ffffff
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#050404
palette = 1=#bd0013
palette = 2=#4ab118
palette = 3=#e7741e
palette = 4=#0f4ac6
palette = 5=#665993
palette = 6=#70a598
palette = 7=#f8dcc0
palette = 8=#4e7cbf
palette = 9=#fc5f5a
palette = 10=#9eff6e
palette = 11=#efc11a
palette = 12=#1997c6
palette = 13=#9b5953
palette = 14=#c8faf4
palette = 15=#f6f5fb
background = #1f1d45
foreground = #f8dcc0
cursor-color = #efbf38
cursor-text = #08080a
selection-background = #706b4e
selection-foreground = #f3d9c4
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#241f31
palette = 1=#c01c28
palette = 2=#2ec27e
palette = 3=#e8b504
palette = 4=#1e78e4
palette = 5=#9841bb
palette = 6=#0ab9dc
palette = 7=#c0bfbc
palette = 8=#5e5c64
palette = 9=#ed333b
palette = 10=#4ad67c
palette = 11=#d2be36
palette = 12=#51a1ff
palette = 13=#c061cb
palette = 14=#4fd2fd
palette = 15=#f6f5f4
background = #ffffff
foreground = #000000
cursor-color = #000000
cursor-text = #ffffff
selection-background = #c0bfbc
selection-foreground = #000000
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#241f31
palette = 1=#c01c28
palette = 2=#2ec27e
palette = 3=#f5c211
palette = 4=#1e78e4
palette = 5=#9841bb
palette = 6=#0ab9dc
palette = 7=#c0bfbc
palette = 8=#5e5c64
palette = 9=#ed333b
palette = 10=#57e389
palette = 11=#f8e45c
palette = 12=#51a1ff
palette = 13=#c061cb
palette = 14=#4fd2fd
palette = 15=#f6f5f4
background = #1d1d20
foreground = #ffffff
cursor-color = #ffffff
cursor-text = #1d1d20
selection-background = #ffffff
selection-foreground = #5e5c64
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#151515
palette = 1=#ac4142
palette = 2=#7e8e50
palette = 3=#e5b567
palette = 4=#6c99bb
palette = 5=#9f4e85
palette = 6=#7dd6cf
palette = 7=#d0d0d0
palette = 8=#505050
palette = 9=#ac4142
palette = 10=#7e8e50
palette = 11=#e5b567
palette = 12=#6c99bb
palette = 13=#9f4e85
palette = 14=#7dd6cf
palette = 15=#f5f5f5
background = #212121
foreground = #d0d0d0
cursor-color = #d0d0d0
cursor-text = #151515
selection-background = #303030
selection-foreground = #d0d0d0
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#aa3731
palette = 2=#448c27
palette = 3=#cb9000
palette = 4=#325cc0
palette = 5=#7a3e9d
palette = 6=#0083b2
palette = 7=#b7b7b7
palette = 8=#777777
palette = 9=#f05050
palette = 10=#60cb00
palette = 11=#f2af50
palette = 12=#007acc
palette = 13=#e64ce6
palette = 14=#00aacb
palette = 15=#f7f7f7
background = #f7f7f7
foreground = #000000
cursor-color = #007acc
cursor-text = #bfdbfe
selection-background = #bfdbfe
selection-foreground = #000000
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#112616
palette = 1=#7f2b27
palette = 2=#2f7e25
palette = 3=#717f24
palette = 4=#2f6a7f
palette = 5=#47587f
palette = 6=#327f77
palette = 7=#647d75
palette = 8=#3c4812
palette = 9=#e08009
palette = 10=#18e000
palette = 11=#bde000
palette = 12=#00aae0
palette = 13=#0058e0
palette = 14=#00e0c4
palette = 15=#73fa91
background = #0f1610
foreground = #637d75
cursor-color = #73fa91
cursor-text = #0f1610
selection-background = #1d4125
selection-foreground = #73fa91
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#cd3131
palette = 2=#05bc79
palette = 3=#e5e512
palette = 4=#2472c8
palette = 5=#bc3fbc
palette = 6=#0fa8cd
palette = 7=#e5e5e5
palette = 8=#666666
palette = 9=#cd3131
palette = 10=#05bc79
palette = 11=#e5e512
palette = 12=#2472c8
palette = 13=#bc3fbc
palette = 14=#0fa8cd
palette = 15=#e5e5e5
background = #262a33
foreground = #e5e5e5
cursor-color = #f8f8f0
cursor-text = #b5b5a8
selection-background = #5a5c62
selection-foreground = #ece7e7
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#c91b00
palette = 2=#00c200
palette = 3=#c7c400
palette = 4=#1c3fe1
palette = 5=#ca30c7
palette = 6=#00c5c7
palette = 7=#c7c7c7
palette = 8=#686868
palette = 9=#ff6e67
palette = 10=#5ffa68
palette = 11=#fffc67
palette = 12=#6871ff
palette = 13=#ff77ff
palette = 14=#60fdff
palette = 15=#ffffff
background = #2c2b2b
foreground = #d5a200
cursor-color = #c7c7c7
cursor-text = #8c8c8c
selection-background = #6b5b02
selection-foreground = #67e000
@@ -0,0 +1,22 @@
palette = 0=#1a1a1a
palette = 1=#cc372e
palette = 2=#26a439
palette = 3=#cdac08
palette = 4=#0869cb
palette = 5=#9647bf
palette = 6=#479ec2
palette = 7=#98989d
palette = 8=#464646
palette = 9=#ff453a
palette = 10=#32d74b
palette = 11=#ffd60a
palette = 12=#0a84ff
palette = 13=#bf5af2
palette = 14=#76d6ff
palette = 15=#ffffff
background = #1e1e1e
foreground = #ffffff
cursor-color = #98989d
cursor-text = #ffffff
selection-background = #3f638b
selection-foreground = #ffffff
@@ -0,0 +1,22 @@
palette = 0=#1a1a1a
palette = 1=#cc372e
palette = 2=#26a439
palette = 3=#cdac08
palette = 4=#0869cb
palette = 5=#9647bf
palette = 6=#479ec2
palette = 7=#98989d
palette = 8=#464646
palette = 9=#ff453a
palette = 10=#32d74b
palette = 11=#e5bc00
palette = 12=#0a84ff
palette = 13=#bf5af2
palette = 14=#69c9f2
palette = 15=#ffffff
background = #feffff
foreground = #000000
cursor-color = #98989d
cursor-text = #ffffff
selection-background = #abd8ff
selection-foreground = #000000
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#333333
palette = 1=#da2700
palette = 2=#12c258
palette = 3=#ffc656
palette = 4=#518bfc
palette = 5=#e37bd9
palette = 6=#63fad5
palette = 7=#bab2b2
palette = 8=#777777
palette = 9=#ffb9b9
palette = 10=#e3f6aa
palette = 11=#ffddaa
palette = 12=#b3e8f3
palette = 13=#cbbaf9
palette = 14=#bcffc7
palette = 15=#efefef
background = #201f1e
foreground = #eee4d9
cursor-color = #872929
cursor-text = #fffbf2
selection-background = #25524a
selection-foreground = #f3fffd
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#2c2c2c
palette = 1=#d3322d
palette = 2=#588b35
palette = 3=#fca93a
palette = 4=#2465c2
palette = 5=#7332b4
palette = 6=#64e1b8
palette = 7=#f7f7f7
palette = 8=#535353
palette = 9=#fa5852
palette = 10=#8dc252
palette = 11=#ffea51
palette = 12=#6ab5f8
palette = 13=#be68ca
palette = 14=#89ffdb
palette = 15=#fefefe
background = #1e1e1e
foreground = #eaeaea
cursor-color = #f7f7f7
cursor-text = #000000
selection-background = #46515e
selection-foreground = #f1f3f5
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#232323
palette = 1=#ff000f
palette = 2=#8ce10b
palette = 3=#ffb900
palette = 4=#008df8
palette = 5=#6d43a6
palette = 6=#00d8eb
palette = 7=#ffffff
palette = 8=#444444
palette = 9=#ff2740
palette = 10=#abe15b
palette = 11=#ffd242
palette = 12=#0092ff
palette = 13=#9a5feb
palette = 14=#67fff0
palette = 15=#ffffff
background = #0e1019
foreground = #fffaf4
cursor-color = #ff0018
cursor-text = #ff8ca4
selection-background = #002a3b
selection-foreground = #ffffff
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#3d352a
palette = 1=#cd5c5c
palette = 2=#86af80
palette = 3=#e8ae5b
palette = 4=#6495ed
palette = 5=#deb887
palette = 6=#b0c4de
palette = 7=#bbaa99
palette = 8=#554444
palette = 9=#cc5533
palette = 10=#88aa22
palette = 11=#ffa75d
palette = 12=#87ceeb
palette = 13=#996600
palette = 14=#b0c4de
palette = 15=#ddccbb
background = #1c1c1c
foreground = #ddeedd
cursor-color = #e2bbef
cursor-text = #000000
selection-background = #4d4d4d
selection-foreground = #ffffff
@@ -0,0 +1,22 @@
palette = 0=#202746
palette = 1=#c94922
palette = 2=#ac9739
palette = 3=#c08b30
palette = 4=#3d8fd1
palette = 5=#6679cc
palette = 6=#22a2c9
palette = 7=#979db4
palette = 8=#6b7394
palette = 9=#c76b29
palette = 10=#4f587c
palette = 11=#5e6687
palette = 12=#898ea4
palette = 13=#dfe2f1
palette = 14=#9c637a
palette = 15=#f5f7ff
background = #202746
foreground = #979db4
cursor-color = #979db4
cursor-text = #202746
selection-background = #5e6687
selection-foreground = #979db4
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#fd5ff1
palette = 2=#87c38a
palette = 3=#ffd7b1
palette = 4=#85befd
palette = 5=#b9b6fc
palette = 6=#85befd
palette = 7=#e0e0e0
palette = 8=#4c4c4c
palette = 9=#fd5ff1
palette = 10=#94fa36
palette = 11=#f5ffa8
palette = 12=#96cbfe
palette = 13=#b9b6fc
palette = 14=#85befd
palette = 15=#e0e0e0
background = #161719
foreground = #c5c8c6
cursor-color = #d0d0d0
cursor-text = #151515
selection-background = #444444
selection-foreground = #c5c8c6
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#21252b
palette = 1=#e06c75
palette = 2=#98c379
palette = 3=#e5c07b
palette = 4=#61afef
palette = 5=#c678dd
palette = 6=#56b6c2
palette = 7=#abb2bf
palette = 8=#767676
palette = 9=#e06c75
palette = 10=#98c379
palette = 11=#e5c07b
palette = 12=#61afef
palette = 13=#c678dd
palette = 14=#56b6c2
palette = 15=#abb2bf
background = #21252b
foreground = #abb2bf
cursor-color = #abb2bf
cursor-text = #21252b
selection-background = #323844
selection-foreground = #abb2bf
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#de3e35
palette = 2=#3f953a
palette = 3=#d2b67c
palette = 4=#2f5af3
palette = 5=#950095
palette = 6=#3f953a
palette = 7=#bbbbbb
palette = 8=#000000
palette = 9=#de3e35
palette = 10=#3f953a
palette = 11=#d2b67c
palette = 12=#2f5af3
palette = 13=#a00095
palette = 14=#3f953a
palette = 15=#ffffff
background = #f9f9f9
foreground = #2a2c33
cursor-color = #bbbbbb
cursor-text = #ffffff
selection-background = #ededed
selection-foreground = #2a2c33
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#110f18
palette = 1=#ff6767
palette = 2=#61ffca
palette = 3=#ffca85
palette = 4=#a277ff
palette = 5=#a277ff
palette = 6=#61ffca
palette = 7=#edecee
palette = 8=#4d4d4d
palette = 9=#ffca85
palette = 10=#a277ff
palette = 11=#ffca85
palette = 12=#a277ff
palette = 13=#a277ff
palette = 14=#61ffca
palette = 15=#edecee
background = #15141b
foreground = #edecee
cursor-color = #a277ff
cursor-text = #edecee
selection-background = #a277ff
selection-foreground = #edecee
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#23262e
palette = 1=#f0266f
palette = 2=#8fd46d
palette = 3=#ffe66d
palette = 4=#102ee4
palette = 5=#ee5d43
palette = 6=#03d6b8
palette = 7=#c74ded
palette = 8=#4f545e
palette = 9=#f92672
palette = 10=#8fd46d
palette = 11=#ffe66d
palette = 12=#03d6b8
palette = 13=#ee5d43
palette = 14=#03d6b8
palette = 15=#c74ded
background = #23262e
foreground = #ffca28
cursor-color = #ee5d43
cursor-text = #ffd29c
selection-background = #292e38
selection-foreground = #00e8c6
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#11151c
palette = 1=#ea6c73
palette = 2=#7fd962
palette = 3=#f9af4f
palette = 4=#53bdfa
palette = 5=#cda1fa
palette = 6=#90e1c6
palette = 7=#c7c7c7
palette = 8=#686868
palette = 9=#f07178
palette = 10=#aad94c
palette = 11=#ffb454
palette = 12=#59c2ff
palette = 13=#d2a6ff
palette = 14=#95e6cb
palette = 15=#ffffff
background = #0b0e14
foreground = #bfbdb6
cursor-color = #e6b450
cursor-text = #0b0e14
selection-background = #409fff
selection-foreground = #0b0e14
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#ea6c6d
palette = 2=#6cbf43
palette = 3=#eca944
palette = 4=#3199e1
palette = 5=#9e75c7
palette = 6=#46ba94
palette = 7=#bababa
palette = 8=#686868
palette = 9=#f07171
palette = 10=#86b300
palette = 11=#f2ae49
palette = 12=#399ee6
palette = 13=#a37acc
palette = 14=#4cbf99
palette = 15=#d1d1d1
background = #f8f9fa
foreground = #5c6166
cursor-color = #ffaa33
cursor-text = #f8f9fa
selection-background = #035bd6
selection-foreground = #f8f9fa
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#171b24
palette = 1=#ed8274
palette = 2=#87d96c
palette = 3=#facc6e
palette = 4=#6dcbfa
palette = 5=#dabafa
palette = 6=#90e1c6
palette = 7=#c7c7c7
palette = 8=#686868
palette = 9=#f28779
palette = 10=#d5ff80
palette = 11=#ffd173
palette = 12=#73d0ff
palette = 13=#dfbfff
palette = 14=#95e6cb
palette = 15=#ffffff
background = #1f2430
foreground = #cccac2
cursor-color = #ffcc66
cursor-text = #1f2430
selection-background = #409fff
selection-foreground = #1f2430
@@ -0,0 +1,22 @@
palette = 0=#17141f
palette = 1=#ff6b7f
palette = 2=#00bd9c
palette = 3=#e6c62f
palette = 4=#22e8df
palette = 5=#dc396a
palette = 6=#56b6c2
palette = 7=#f1f1f1
palette = 8=#495162
palette = 9=#fe9ea1
palette = 10=#98c379
palette = 11=#f9e46b
palette = 12=#91fff4
palette = 13=#da70d6
palette = 14=#bcf3ff
palette = 15=#ffffff
background = #191323
foreground = #cccccc
cursor-color = #e07d13
cursor-text = #ffffff
selection-background = #220525
selection-foreground = #f4f4f4
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#1b1d1e
palette = 1=#e6dc44
palette = 2=#c8be46
palette = 3=#f4fd22
palette = 4=#737174
palette = 5=#747271
palette = 6=#62605f
palette = 7=#c6c5bf
palette = 8=#505354
palette = 9=#fff78e
palette = 10=#fff27d
palette = 11=#feed6c
palette = 12=#919495
palette = 13=#9a9a9d
palette = 14=#a3a3a6
palette = 15=#dadbd6
background = #1b1d1e
foreground = #6f6f6f
cursor-color = #fcef0c
cursor-text = #000000
selection-background = #4d504c
selection-foreground = #f0e04a
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#20111b
palette = 1=#be100e
palette = 2=#858162
palette = 3=#d08b30
palette = 4=#426a79
palette = 5=#97522c
palette = 6=#989a9c
palette = 7=#968c83
palette = 8=#5e5252
palette = 9=#be100e
palette = 10=#858162
palette = 11=#d08b30
palette = 12=#426a79
palette = 13=#97522c
palette = 14=#989a9c
palette = 15=#d5ccba
background = #d5ccba
foreground = #45373c
cursor-color = #45373c
cursor-text = #d5ccba
selection-background = #968c83
selection-foreground = #45373c
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#20111b
palette = 1=#be100e
palette = 2=#858162
palette = 3=#eaa549
palette = 4=#426a79
palette = 5=#97522c
palette = 6=#989a9c
palette = 7=#968c83
palette = 8=#5e5252
palette = 9=#be100e
palette = 10=#858162
palette = 11=#eaa549
palette = 12=#426a79
palette = 13=#97522c
palette = 14=#989a9c
palette = 15=#d5ccba
background = #20111b
foreground = #968c83
cursor-color = #968c83
cursor-text = #20111b
selection-background = #45373c
selection-foreground = #968c83
@@ -0,0 +1,22 @@
palette = 0=#573d26
palette = 1=#be2d26
palette = 2=#6ba18a
palette = 3=#e99d2a
palette = 4=#5a86ad
palette = 5=#ac80a6
palette = 6=#74a6ad
palette = 7=#e0dbb7
palette = 8=#9b6c4a
palette = 9=#e84627
palette = 10=#95d8ba
palette = 11=#d0d150
palette = 12=#b8d3ed
palette = 13=#d19ecb
palette = 14=#93cfd7
palette = 15=#fff9d5
background = #2a1f1d
foreground = #e0dbb7
cursor-color = #644a33
cursor-text = #8a7059
selection-background = #563c27
selection-foreground = #e0dbbb
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#486e6f
palette = 2=#dd9999
palette = 3=#a06666
palette = 4=#888888
palette = 5=#999999
palette = 6=#aaaaaa
palette = 7=#c1c1c1
palette = 8=#404040
palette = 9=#486e6f
palette = 10=#dd9999
palette = 11=#a06666
palette = 12=#888888
palette = 13=#999999
palette = 14=#aaaaaa
palette = 15=#c1c1c1
background = #000000
foreground = #c1c1c1
cursor-color = #c1c1c1
cursor-text = #8e8e8e
selection-background = #c1c1c1
selection-foreground = #000000
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#5f8787
palette = 2=#fbcb97
palette = 3=#e78a53
palette = 4=#888888
palette = 5=#999999
palette = 6=#aaaaaa
palette = 7=#c1c1c1
palette = 8=#404040
palette = 9=#5f8787
palette = 10=#fbcb97
palette = 11=#e78a53
palette = 12=#888888
palette = 13=#999999
palette = 14=#aaaaaa
palette = 15=#c1c1c1
background = #000000
foreground = #c1c1c1
cursor-color = #c1c1c1
cursor-text = #8e8e8e
selection-background = #c1c1c1
selection-foreground = #000000
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#5f8787
palette = 2=#ddeecc
palette = 3=#99bbaa
palette = 4=#888888
palette = 5=#999999
palette = 6=#aaaaaa
palette = 7=#c1c1c1
palette = 8=#404040
palette = 9=#5f8787
palette = 10=#ddeecc
palette = 11=#99bbaa
palette = 12=#888888
palette = 13=#999999
palette = 14=#aaaaaa
palette = 15=#c1c1c1
background = #000000
foreground = #c1c1c1
cursor-color = #c1c1c1
cursor-text = #8e8e8e
selection-background = #c1c1c1
selection-foreground = #000000
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#5f8787
palette = 2=#d0dfee
palette = 3=#5f81a5
palette = 4=#888888
palette = 5=#999999
palette = 6=#aaaaaa
palette = 7=#c1c1c1
palette = 8=#404040
palette = 9=#5f8787
palette = 10=#d0dfee
palette = 11=#5f81a5
palette = 12=#888888
palette = 13=#999999
palette = 14=#aaaaaa
palette = 15=#c1c1c1
background = #000000
foreground = #c1c1c1
cursor-color = #c1c1c1
cursor-text = #8e8e8e
selection-background = #c1c1c1
selection-foreground = #000000
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#5f8787
palette = 2=#9b8d7f
palette = 3=#8c7f70
palette = 4=#888888
palette = 5=#999999
palette = 6=#aaaaaa
palette = 7=#c1c1c1
palette = 8=#404040
palette = 9=#5f8787
palette = 10=#9b8d7f
palette = 11=#8c7f70
palette = 12=#888888
palette = 13=#999999
palette = 14=#aaaaaa
palette = 15=#c1c1c1
background = #000000
foreground = #c1c1c1
cursor-color = #c1c1c1
cursor-text = #8e8e8e
selection-background = #c1c1c1
selection-foreground = #000000
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#5f8787
palette = 2=#7799bb
palette = 3=#556677
palette = 4=#888888
palette = 5=#999999
palette = 6=#aaaaaa
palette = 7=#c1c1c1
palette = 8=#404040
palette = 9=#5f8787
palette = 10=#7799bb
palette = 11=#556677
palette = 12=#888888
palette = 13=#999999
palette = 14=#aaaaaa
palette = 15=#c1c1c1
background = #000000
foreground = #c1c1c1
cursor-color = #c1c1c1
cursor-text = #8e8e8e
selection-background = #c1c1c1
selection-foreground = #000000
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#5f8787
palette = 2=#eceee3
palette = 3=#974b46
palette = 4=#888888
palette = 5=#999999
palette = 6=#aaaaaa
palette = 7=#c1c1c1
palette = 8=#404040
palette = 9=#5f8787
palette = 10=#eceee3
palette = 11=#974b46
palette = 12=#888888
palette = 13=#999999
palette = 14=#aaaaaa
palette = 15=#c1c1c1
background = #000000
foreground = #c1c1c1
cursor-color = #c1c1c1
cursor-text = #8e8e8e
selection-background = #c1c1c1
selection-foreground = #000000
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#5f8787
palette = 2=#a5aaa7
palette = 3=#626b67
palette = 4=#888888
palette = 5=#999999
palette = 6=#aaaaaa
palette = 7=#c1c1c1
palette = 8=#404040
palette = 9=#5f8787
palette = 10=#a5aaa7
palette = 11=#626b67
palette = 12=#888888
palette = 13=#999999
palette = 14=#aaaaaa
palette = 15=#c1c1c1
background = #000000
foreground = #c1c1c1
cursor-color = #c1c1c1
cursor-text = #8e8e8e
selection-background = #c1c1c1
selection-foreground = #000000
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#5f8787
palette = 2=#f3ecd4
palette = 3=#eecc6c
palette = 4=#888888
palette = 5=#999999
palette = 6=#aaaaaa
palette = 7=#c1c1c1
palette = 8=#404040
palette = 9=#5f8787
palette = 10=#f3ecd4
palette = 11=#eecc6c
palette = 12=#888888
palette = 13=#999999
palette = 14=#aaaaaa
palette = 15=#c1c1c1
background = #000000
foreground = #c1c1c1
cursor-color = #c1c1c1
cursor-text = #8e8e8e
selection-background = #c1c1c1
selection-foreground = #000000
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#5f8787
palette = 2=#aa9988
palette = 3=#777755
palette = 4=#888888
palette = 5=#999999
palette = 6=#aaaaaa
palette = 7=#c1c1c1
palette = 8=#404040
palette = 9=#5f8787
palette = 10=#aa9988
palette = 11=#777755
palette = 12=#888888
palette = 13=#999999
palette = 14=#aaaaaa
palette = 15=#c1c1c1
background = #000000
foreground = #c1c1c1
cursor-color = #c1c1c1
cursor-text = #8e8e8e
selection-background = #c1c1c1
selection-foreground = #000000
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#5f8787
palette = 2=#f8f7f2
palette = 3=#79241f
palette = 4=#888888
palette = 5=#999999
palette = 6=#aaaaaa
palette = 7=#c1c1c1
palette = 8=#404040
palette = 9=#5f8787
palette = 10=#f8f7f2
palette = 11=#79241f
palette = 12=#888888
palette = 13=#999999
palette = 14=#aaaaaa
palette = 15=#c1c1c1
background = #000000
foreground = #c1c1c1
cursor-color = #c1c1c1
cursor-text = #8e8e8e
selection-background = #c1c1c1
selection-foreground = #000000
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#b87a7a
palette = 2=#7ab87a
palette = 3=#b8b87a
palette = 4=#7a7ab8
palette = 5=#b87ab8
palette = 6=#7ab8b8
palette = 7=#d9d9d9
palette = 8=#4c4c4c
palette = 9=#dbbdbd
palette = 10=#bddbbd
palette = 11=#dbdbbd
palette = 12=#bdbddb
palette = 13=#dbbddb
palette = 14=#bddbdb
palette = 15=#ffffff
background = #0d1926
foreground = #d9e6f2
cursor-color = #d9e6f2
cursor-text = #0d1926
selection-background = #c1ddff
selection-foreground = #000000
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#0a4c62
palette = 1=#99246e
palette = 2=#5cb1b3
palette = 3=#eab9a8
palette = 4=#90a5bd
palette = 5=#9d54a7
palette = 6=#7e83cc
palette = 7=#f0e8d6
palette = 8=#463c5d
palette = 9=#c87272
palette = 10=#0a6c7e
palette = 11=#7a3188
palette = 12=#5f3d63
palette = 13=#bc94b7
palette = 14=#5e6071
palette = 15=#0a6c7e
background = #1c0c28
foreground = #babab9
cursor-color = #fcfad6
cursor-text = #000000
selection-background = #606060
selection-foreground = #ffffff
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#292d3e
palette = 1=#ff8288
palette = 2=#b4e88d
palette = 3=#f4d69f
palette = 4=#82aaff
palette = 5=#e9c1ff
palette = 6=#89ebff
palette = 7=#d0d0d0
palette = 8=#9094a4
palette = 9=#ff8b92
palette = 10=#ddffa7
palette = 11=#ffe585
palette = 12=#9cc4ff
palette = 13=#ddb0f6
palette = 14=#a3f7ff
palette = 15=#ffffff
background = #006984
foreground = #c5f2ff
cursor-color = #ffcc00
cursor-text = #292d3e
selection-background = #2baeca
selection-foreground = #eceff1
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#101116
palette = 1=#ff5680
palette = 2=#00ff9c
palette = 3=#fffc58
palette = 4=#00b0ff
palette = 5=#d57bff
palette = 6=#76c1ff
palette = 7=#c7c7c7
palette = 8=#686868
palette = 9=#ff6e67
palette = 10=#5ffa68
palette = 11=#fffc67
palette = 12=#6871ff
palette = 13=#d682ec
palette = 14=#60fdff
palette = 15=#ffffff
background = #101116
foreground = #00a2ff
cursor-color = #76ff9f
cursor-text = #a6a6a6
selection-background = #c1deff
selection-foreground = #000000
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#41444d
palette = 1=#fc2f52
palette = 2=#25a45c
palette = 3=#ff936a
palette = 4=#3476ff
palette = 5=#7a82da
palette = 6=#4483aa
palette = 7=#cdd4e0
palette = 8=#8f9aae
palette = 9=#ff6480
palette = 10=#3fc56b
palette = 11=#f9c859
palette = 12=#10b1fe
palette = 13=#ff78f8
palette = 14=#5fb9bc
palette = 15=#ffffff
background = #282c34
foreground = #b9c0cb
cursor-color = #ffcc00
cursor-text = #282c34
selection-background = #b9c0ca
selection-foreground = #272b33
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#373a41
palette = 1=#d52753
palette = 2=#23974a
palette = 3=#df631c
palette = 4=#275fe4
palette = 5=#823ff1
palette = 6=#27618d
palette = 7=#babbc2
palette = 8=#676a77
palette = 9=#ff6480
palette = 10=#3cbc66
palette = 11=#c5a332
palette = 12=#0099e1
palette = 13=#ce33c0
palette = 14=#6d93bb
palette = 15=#d3d3d3
background = #f9f9f9
foreground = #373a41
cursor-color = #f32759
cursor-text = #ffffff
selection-background = #daf0ff
selection-foreground = #373a41
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#4f4f4f
palette = 1=#ff6c60
palette = 2=#a8ff60
palette = 3=#ffffb6
palette = 4=#96cbfe
palette = 5=#ff73fd
palette = 6=#c6c5fe
palette = 7=#eeeeee
palette = 8=#7c7c7c
palette = 9=#ffb6b0
palette = 10=#ceffac
palette = 11=#ffffcc
palette = 12=#b5dcff
palette = 13=#ff9cfe
palette = 14=#dfdffe
palette = 15=#ffffff
background = #0000a4
foreground = #ffff4e
cursor-color = #ffa560
cursor-text = #ffffff
selection-background = #a4a4a4
selection-foreground = #0000a4
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#cc0403
palette = 2=#19cb00
palette = 3=#cecb00
palette = 4=#0d73cc
palette = 5=#cb1ed1
palette = 6=#0dcdcd
palette = 7=#dddddd
palette = 8=#767676
palette = 9=#f2201f
palette = 10=#23fd00
palette = 11=#fffd00
palette = 12=#1a8fff
palette = 13=#fd28ff
palette = 14=#14ffff
palette = 15=#ffffff
background = #141d2b
foreground = #9fef00
cursor-color = #9fef00
cursor-text = #111111
selection-background = #a4b1cd
selection-foreground = #141d2b
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#362c24
palette = 1=#b10b00
palette = 2=#007232
palette = 3=#8b4c00
palette = 4=#005cb4
palette = 5=#9b0097
palette = 6=#006a78
palette = 7=#baa99d
palette = 8=#514337
palette = 9=#de1100
palette = 10=#008f40
palette = 11=#ae6000
palette = 12=#0074e1
palette = 13=#c300bd
palette = 14=#008697
palette = 15=#eae1da
background = #f1ebe6
foreground = #362c24
cursor-color = #362c24
cursor-text = #f1ebe6
selection-background = #362c24
selection-foreground = #f1ebe6
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#31363b
palette = 1=#ed1515
palette = 2=#11d116
palette = 3=#f67400
palette = 4=#1d99f3
palette = 5=#9b59b6
palette = 6=#1abc9c
palette = 7=#eff0f1
palette = 8=#7f8c8d
palette = 9=#c0392b
palette = 10=#1cdc9a
palette = 11=#fdbc4b
palette = 12=#3daee9
palette = 13=#8e44ad
palette = 14=#16a085
palette = 15=#fcfcfc
background = #31363b
foreground = #eff0f1
cursor-color = #eff0f1
cursor-text = #31363b
selection-background = #eff0f1
selection-foreground = #31363b
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#191919
palette = 1=#ff355b
palette = 2=#b7e876
palette = 3=#ffc251
palette = 4=#76d4ff
palette = 5=#ba76e7
palette = 6=#6cbfb5
palette = 7=#c2c8d7
palette = 8=#4c4c4c
palette = 9=#ff355b
palette = 10=#b7e876
palette = 11=#ffc251
palette = 12=#76d5ff
palette = 13=#ba76e7
palette = 14=#6cbfb5
palette = 15=#c2c8d7
background = #191919
foreground = #b3c9d7
cursor-color = #f34b00
cursor-text = #002831
selection-background = #b3c9d7
selection-foreground = #191919
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#000000
palette = 1=#da4939
palette = 2=#519f50
palette = 3=#ffd24a
palette = 4=#6d9cbe
palette = 5=#d0d0ff
palette = 6=#6e9cbe
palette = 7=#ffffff
palette = 8=#585858
palette = 9=#ff7b6b
palette = 10=#83d182
palette = 11=#ffff7c
palette = 12=#9fcef0
palette = 13=#ffffff
palette = 14=#a0cef0
palette = 15=#ffffff
background = #2b2b2b
foreground = #e6e1dc
cursor-color = #ffffff
cursor-text = #c0bbb6
selection-background = #5a647e
selection-foreground = #e6e1dc
+22
View File
@@ -0,0 +1,22 @@
palette = 0=#1f1f1f
palette = 1=#f81118
palette = 2=#2dc55e
palette = 3=#ecba0f
palette = 4=#2a84d2
palette = 5=#4e5ab7
palette = 6=#1081d6
palette = 7=#d6dbe5
palette = 8=#d6dbe5
palette = 9=#de352e
palette = 10=#1dd361
palette = 11=#f3bd09
palette = 12=#1081d6
palette = 13=#5350b9
palette = 14=#0f7ddb
palette = 15=#ffffff
background = #131313
foreground = #d6dbe5
cursor-color = #b9b9b9
cursor-text = #101010
selection-background = #1f1f1f
selection-foreground = #d6dbe5

Some files were not shown because too many files have changed in this diff Show More