Reworked the panel. Now it's a mode, like ratpoison mode. New items include panel mode w for weather, and panel mode s for system information. More coming if this turns out to work well. New dependencies added to readme.

This commit is contained in:
Storm Dragon 2025-04-06 19:40:55 -04:00
parent 8064eaa6e2
commit dc8f832840
7 changed files with 862 additions and 579 deletions

566
I38.html
View File

@ -1,566 +0,0 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>Welcome to I38</title>
<style>
html {
color: #1a1a1a;
background-color: #fdfdfd;
}
body {
margin: 0 auto;
max-width: 36em;
padding-left: 50px;
padding-right: 50px;
padding-top: 50px;
padding-bottom: 50px;
hyphens: auto;
overflow-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
body {
font-size: 0.9em;
padding: 12px;
}
h1 {
font-size: 1.8em;
}
}
@media print {
html {
background-color: white;
}
body {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
svg {
height: auto;
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, Consolas, 'Lucida Console', monospace;
font-size: 85%;
margin: 0;
hyphens: manual;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
overflow-wrap: normal;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
border: none;
border-top: 1px solid #1a1a1a;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC ul {
padding-left: 1.3em;
}
#TOC > ul {
padding-left: 0;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
/* The extra [class] is a hack that increases specificity enough to
override a similar rule in reveal.js */
ul.task-list[class]{list-style: none;}
ul.task-list li input[type="checkbox"] {
font-size: inherit;
width: 0.8em;
margin: 0 0.8em 0.2em -1.6em;
vertical-align: middle;
}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
</head>
<body>
<header id="title-block-header">
<h1 class="title">Welcome to I38</h1>
</header>
<h1 id="welcome-to-i38---accessible-i3-window-manager">Welcome to I38 -
Accessible i3 Window Manager</h1>
<blockquote>
<p><strong>Note:</strong> This help guide has been tailored to your
specific configuration. Youve chosen <strong>BROWSER</strong> as your
web browser, <strong>MODKEY</strong> as your mod key, and youre using
the <strong>SCREENREADER</strong> screen reader.</p>
</blockquote>
<h2 id="introduction-to-i38">Introduction to I38</h2>
<p>I38 is a configuration for the i3 window manager that makes it more
accessible for blind people. It features audio feedback, screen reader
integration, and keyboard shortcuts designed for non-visual
navigation.</p>
<p>Unlike traditional desktop environments like GNOME or MATE, i3 is a
tiling window manager, which means windows are arranged in a
non-overlapping layout. This can be more efficient to navigate by
keyboard, as windows are organized in a predictable structure.</p>
<h3 id="coming-from-gnome-or-mate">Coming from GNOME or MATE?</h3>
<p>If youre transitioning from GNOME or MATE, here are some key
differences to understand:</p>
<ul>
<li><strong>Window Management</strong>: In GNOME/MATE, windows can
overlap freely and are typically manipulated with a mouse. In i3/I38,
windows tile automatically and are primarily controlled with keyboard
shortcuts.</li>
<li><strong>Panels and Indicators</strong>: Instead of persistent panels
with menus and indicators, I38 uses keyboard shortcuts to access
functionality.</li>
<li><strong>Workspace Navigation</strong>: While GNOME/MATE have
workspaces that you can switch between, I38s workspaces are more
central to the workflow and are accessed via dedicated keyboard
shortcuts.</li>
<li><strong>Application Launching</strong>: Rather than using a start
menu or activities overview, I38 provides keyboard shortcuts for
launching applications.</li>
</ul>
<p>I38 has been configured to make this transition easier by providing a
tabbed layout (similar to browser tabs) and shortcuts that may feel
somewhat familiar.</p>
<h2 id="basic-concepts">Basic Concepts</h2>
<h3 id="workspaces">Workspaces</h3>
<p>Workspaces act like virtual desktops, allowing you to organize
applications. You have 10 workspaces available.</p>
<ul>
<li>Switch to workspace: <code>Control</code> + <code>F1</code> through
<code>F10</code></li>
<li>Move window to workspace: <code>Control</code> + <code>Shift</code>
+ <code>F1</code> through <code>F10</code></li>
</ul>
<p><em>GNOME/MATE comparison:</em> Similar to workspaces in GNOME/MATE,
but with dedicated keyboard shortcuts rather than overview modes or
workspace switchers.</p>
<h3 id="window-management">Window Management</h3>
<p>Windows in I38 are arranged in a tabbed layout by default, which
means windows take up the entire screen and you can switch between them
like browser tabs.</p>
<ul>
<li>Switch between windows: <code>Alt</code> + <code>Tab</code> (next)
or <code>Alt</code> + <code>Shift</code> + <code>Tab</code>
(previous)</li>
<li>Launch terminal: <code>MODKEY</code> + <code>Return</code></li>
<li>Close window: <code>MODKEY</code> + <code>F4</code></li>
<li>Toggle fullscreen: <code>MODKEY</code> + <code>BackSpace</code></li>
<li>List windows in current workspace: <code>RATPOISONKEY</code> then
<code>'</code> (apostrophe)</li>
</ul>
<p><em>GNOME/MATE comparison:</em> Alt+Tab works similarly to
GNOME/MATE, but window placement is automatic rather than manual.</p>
<h2 id="modes-in-i38">Modes in I38</h2>
<h3 id="default-mode">Default Mode</h3>
<p>This is the standard mode for working with applications. Most
commands start with your mod key (<code>MODKEY</code>).</p>
<h3 id="ratpoison-mode">Ratpoison Mode</h3>
<p>Ratpoison mode allows quick access to common actions using shorter
key combinations. To enter Ratpoison mode, press
<code>RATPOISONKEY</code>. After pressing this key, you can execute
commands with single keystrokes.</p>
<p>Common Ratpoison mode commands:</p>
<table>
<colgroup>
<col style="width: 38%" />
<col style="width: 61%" />
</colgroup>
<thead>
<tr>
<th>Key</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>c</code></td>
<td>Launch a terminal</td>
</tr>
<tr>
<td><code>e</code></td>
<td>Open text editor (TEXTEDITOR)</td>
</tr>
<tr>
<td><code>w</code></td>
<td>Launch web browser (BROWSER)</td>
</tr>
<tr>
<td><code>k</code></td>
<td>Kill (close) the current window</td>
</tr>
<tr>
<td><code>?</code></td>
<td>Show I38 help</td>
</tr>
<tr>
<td><code>Escape</code> or <code>Control</code> + <code>g</code></td>
<td>Exit Ratpoison mode without taking action</td>
</tr>
<tr>
<td><code>Shift</code> + <code>c</code></td>
<td>Restart Cthulhu screen reader</td>
</tr>
<tr>
<td><code>Shift</code> + <code>o</code></td>
<td>Restart Orca screen reader</td>
</tr>
<tr>
<td><code>Shift</code> + <code>t</code></td>
<td>Toggle screen reader</td>
</tr>
<tr>
<td><code>Control</code> + <code>;</code></td>
<td>Reload I38 configuration</td>
</tr>
<tr>
<td><code>Control</code> + <code>q</code></td>
<td>Exit i3 (log out)</td>
</tr>
<tr>
<td><code>!</code></td>
<td>Open run dialog</td>
</tr>
<tr>
<td><code>Alt</code> + <code>b</code></td>
<td>Check battery status</td>
</tr>
<tr>
<td><code>g</code></td>
<td>Check game controller status</td>
</tr>
</tbody>
</table>
<p><em>GNOME/MATE comparison:</em> This mode has no direct equivalent in
GNOME/MATE. Think of it as a command palette or quick launcher activated
by a single key.</p>
<h3 id="bypass-mode">Bypass Mode</h3>
<p>Bypass mode passes all keys directly to the application, which is
useful for applications that need many keyboard shortcuts. To enter
bypass mode, press <code>MODKEY</code> + <code>Shift</code> +
<code>BackSpace</code>. Use the same key combination to exit bypass
mode.</p>
<p><em>GNOME/MATE comparison:</em> In GNOME/MATE, applications always
receive keyboard input directly. Bypass mode simulates this behavior
within i3.</p>
<h2 id="accessibility-features">Accessibility Features</h2>
<h3 id="screen-reader">Screen Reader</h3>
<p>I38 is configured to work with your screen reader (SCREENREADER). The
screen reader will provide spoken feedback about whats happening on
screen so long as there is a window. If you dont have a window open and
need to change something SCREENREADER related, press Control+Alt+d to
bring up the desktop, then screen reader keys should work.</p>
<ul>
<li>Toggle screen reader: <code>RATPOISONKEY</code> then
<code>Shift</code> + <code>t</code></li>
<li>Restart screen reader: <code>RATPOISONKEY</code> then
<code>Shift</code> + <code>o</code> (for Orca) or <code>Shift</code> +
<code>c</code> (for Cthulhu)</li>
<li>Interrupt speech: <code>MODKEY</code> + <code>Shift</code> +
<code>F5</code></li>
</ul>
<p><em>GNOME/MATE comparison:</em> GNOME uses Orca by default with its
own keyboard shortcuts. I38 integrates screen readers more deeply with
the window manager.</p>
<h3 id="braille-display-support">Braille Display Support</h3>
<p>If youve enabled braille display support during setup, I38 will
start XBrlAPI automatically to provide braille output from your screen
reader.</p>
<h3 id="ocr-optical-character-recognition">OCR (Optical Character
Recognition)</h3>
<p>If installed, you can use OCR to read text from images or
inaccessible applications:</p>
<ul>
<li><code>MODKEY</code> + <code>F5</code>: Perform OCR on the entire
screen and speak the content</li>
<li>In Ratpoison mode: <code>Print</code> or <code>MODKEY</code> +
<code>r</code>: Perform OCR and save to clipboard</li>
</ul>
<p><em>GNOME/MATE comparison:</em> OCR features are typically not
integrated into GNOME/MATE by default.</p>
<h3 id="sound-effects">Sound Effects</h3>
<p>I38 provides audio feedback for many actions:</p>
<ul>
<li>Window open/close: Ascending/descending tones</li>
<li>Mode changes: Distinctive sounds for each mode</li>
<li>Workspace changes: Subtle audio cues</li>
<li>Fullscreen toggle: Special sound effect</li>
</ul>
<p>This audio feedback provides non-visual confirmation of actions and
state changes.</p>
<p><em>GNOME/MATE comparison:</em> GNOME/MATE typically have fewer sound
effects for window management actions.</p>
<h2 id="application-menu-and-running-programs">Application Menu and
Running Programs</h2>
<p>Access applications in multiple ways:</p>
<ul>
<li>Applications menu: <code>MODKEY</code> + <code>F1</code></li>
<li>Run dialog (enter a command): <code>MODKEY</code> + <code>F2</code>
or in Ratpoison mode, <code>!</code> (exclamation mark)</li>
<li>Common applications have dedicated shortcuts in Ratpoison mode (see
table above)</li>
</ul>
<p>The applications menu is organized by categories similar to
traditional desktop environments.</p>
<p><em>GNOME/MATE comparison:</em> Instead of clicking on application
icons or using a start menu, I38 provides keyboard shortcuts to access
applications.</p>
<h2 id="reminders-and-notifications">Reminders and Notifications</h2>
<p>I38 includes integration with the <code>remind</code> program for
managing reminders:</p>
<ul>
<li>Access the reminder tool: <code>RATPOISONKEY</code> then
<code>r</code></li>
<li>Create various types of reminders (one-time, daily, weekly,
monthly)</li>
<li>Get notification alerts for your reminders</li>
</ul>
<p>The reminder tool provides the following features:</p>
<ul>
<li><strong>One-time Reminders</strong>: Set for a specific date and
time</li>
<li><strong>Daily Reminders</strong>: Occur every day at the specified
time</li>
<li><strong>Weekly Reminders</strong>: Occur on specific days of the
week</li>
<li><strong>Monthly Reminders</strong>: Occur on a specific day each
month or the last day of each month</li>
<li><strong>Custom Reminders</strong>: Create complex reminder
patterns</li>
</ul>
<p><em>GNOME/MATE comparison:</em> Similar to calendar applications in
GNOME/MATE but with a simplified interface optimized for keyboard
navigation.</p>
<h2 id="volume-and-media-controls">Volume and Media Controls</h2>
<h3 id="system-volume">System Volume</h3>
<ul>
<li>Increase volume: <code>MODKEY</code> +
<code>XF86AudioRaiseVolume</code></li>
<li>Decrease volume: <code>MODKEY</code> +
<code>XF86AudioLowerVolume</code></li>
<li>Mute/unmute: <code>MODKEY</code> + <code>XF86AudioMute</code></li>
</ul>
<h3 id="media-player-controls">Media Player Controls</h3>
<ul>
<li>Play/Pause: <code>XF86AudioPlay</code></li>
<li>Next track: <code>XF86AudioNext</code></li>
<li>Previous track: <code>XF86AudioPrev</code></li>
<li>Stop: <code>XF86AudioStop</code></li>
<li>Media info: <code>MODKEY</code> + <code>XF86AudioPlay</code></li>
</ul>
<p>In Ratpoison mode, these are also available with Alt+Shift
combinations:</p>
<ul>
<li>Increase volume: <code>Alt</code> + <code>Shift</code> +
<code>=</code></li>
<li>Decrease volume: <code>Alt</code> + <code>Shift</code> +
<code>-</code></li>
<li>Previous track: <code>Alt</code> + <code>Shift</code> +
<code>z</code></li>
<li>Pause: <code>Alt</code> + <code>Shift</code> + <code>c</code></li>
<li>Play: <code>Alt</code> + <code>Shift</code> + <code>x</code></li>
<li>Stop: <code>Alt</code> + <code>Shift</code> + <code>v</code></li>
<li>Next track: <code>Alt</code> + <code>Shift</code> +
<code>b</code></li>
<li>Media info: <code>Alt</code> + <code>Shift</code> +
<code>u</code></li>
</ul>
<p><em>GNOME/MATE comparison:</em> Media controls are similar to those
in GNOME/MATE, with the addition of audio feedback.</p>
<h2 id="file-management">File Management</h2>
<p>I38 uses FILEBROWSER for file management. Launch it in Ratpoison mode
with the <code>f</code> key.</p>
<p><em>GNOME/MATE comparison:</em> Similar functionality to Nautilus
(GNOME) or Caja (MATE), but launched via keyboard shortcut rather than
from a desktop icon or menu.</p>
<h2 id="system-operations">System Operations</h2>
<ul>
<li>Reload I38 configuration: In Ratpoison mode, <code>Control</code> +
<code>;</code> (semicolon)</li>
<li>Exit i3 (log out): In Ratpoison mode, <code>Control</code> +
<code>q</code></li>
<li>Check battery status: In Ratpoison mode, <code>Alt</code> +
<code>b</code></li>
<li>Check game controller status: In Ratpoison mode, <code>g</code></li>
<li>Adjust screen brightness (if xrandr is available): In Ratpoison
mode, <code>Alt</code> + <code>s</code></li>
</ul>
<p><em>GNOME/MATE comparison:</em> These functions are typically
available through system menus or indicators in GNOME/MATE.</p>
<h2 id="keyboard-layouts">Keyboard Layouts</h2>
<p>Switch between layouts: <code>Super</code> + <code>Space</code></p>
<p>This is only available if you chose multiple keyboard layouts during
setup.</p>
<p><em>GNOME/MATE comparison:</em> Similar to keyboard layout switching
in GNOME/MATE, but with a different default shortcut.</p>
<h2 id="desktop-and-window-decorations">Desktop and Window
Decorations</h2>
<p>Unlike GNOME or MATE, i3 uses minimal window decorations. Windows
dont have title bars with minimize/maximize buttons. Instead, windows
fill their available space automatically, and interactions are performed
through keyboard shortcuts.</p>
<ul>
<li>Show desktop icons: <code>MODKEY</code> + <code>Control</code> +
<code>d</code></li>
</ul>
<h2 id="clipboard-management">Clipboard Management</h2>
<p>I38 includes clipboard management features:</p>
<ul>
<li>Access clipboard history: <code>MODKEY</code> + <code>Control</code>
+ <code>c</code></li>
</ul>
<h2 id="bookmark-management">Bookmark Management</h2>
<ul>
<li>Access bookmarks: <code>MODKEY</code> + <code>Control</code> +
<code>b</code></li>
</ul>
<p><em>GNOME/MATE comparison:</em> Bookmarks are typically managed
within applications in GNOME/MATE. I38 provides a system-wide bookmark
manager.</p>
<h2 id="tips-for-new-users">Tips for New Users</h2>
<ul>
<li><strong>Start with Ratpoison mode</strong>: Learn the single-key
commands first, as theyre easier to remember.</li>
<li><strong>Use the window list</strong>: When youre lost, use
<code>RATPOISONKEY</code> then <code>'</code> to show all windows in the
current workspace.</li>
<li><strong>Bookmark important websites</strong>: Use
<code>MODKEY</code> + <code>Control</code> + <code>b</code> to access
bookmarks.</li>
<li><strong>Remember the help shortcut</strong>: <code>MODKEY</code> +
<code>Shift</code> + <code>F1</code> is your friend when you need
guidance.</li>
<li><strong>Let the sound effects guide you</strong>: Pay attention to
the audio cues to understand whats happening.</li>
<li><strong>Take advantage of OCR</strong>: If an application isnt
accessible, try the OCR function.</li>
</ul>
<h3 id="transitioning-from-gnomemate">Transitioning from GNOME/MATE</h3>
<ul>
<li>Start by learning the basic navigation shortcuts before exploring
advanced features</li>
<li>The tabbed layout should feel somewhat familiar if youre used to
browser tabs</li>
<li>Alt+Tab works similarly to GNOME/MATE for switching between
windows</li>
<li>Focus on keyboard commands rather than looking for visual elements
like panels or docks</li>
</ul>
<h2 id="customization">Customization</h2>
<p>You can customize I38 by editing the file
<code>~/.config/i3/customizations</code>. This file will not be
overwritten when you update I38.</p>
<p>Example customizations:</p>
<pre><code># Change background color
exec_always --no-startup-id xsetroot -solid &quot;#2E3440&quot;
# Add custom keybinding
bindsym $mod+F12 exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ 100%</code></pre>
<p>To reconfigure I38 completely, run the <code>i38.sh</code> script
again.</p>
<p><em>GNOME/MATE comparison:</em> Much more text-based configuration
compared to the graphical settings dialogs in GNOME/MATE.</p>
<h2 id="getting-help">Getting Help</h2>
<p>If you need assistance with I38, you can:</p>
<ul>
<li>Press <code>MODKEY</code> + <code>Shift</code> + <code>F1</code> to
view the help documentation</li>
<li>Visit the Stormux website at <a
href="https://stormux.org">stormux.org</a></li>
<li>Check the i3 documentation at <a
href="https://i3wm.org/docs/userguide.html">i3wm.org/docs/userguide.html</a></li>
</ul>
<hr />
<p><em>I38 - Making i3 accessible. A Stormux project. License: GPL
v3</em></p>
</body>
</html>

265
I38.md Normal file
View File

@ -0,0 +1,265 @@
# Welcome to I38 - Accessible i3 Window Manager
> **Note:** This help guide has been tailored to your specific configuration. You've chosen **BROWSER** as your web browser, **MODKEY** as your mod key, and you're using the **SCREENREADER** screen reader.
## Introduction to I38
I38 is a configuration for the i3 window manager that makes it more accessible for blind people. It features audio feedback, screen reader integration, and keyboard shortcuts designed for non-visual navigation.
Unlike traditional desktop environments like GNOME or MATE, i3 is a tiling window manager, which means windows are arranged in a non-overlapping layout. This can be more efficient to navigate by keyboard, as windows are organized in a predictable structure.
### Coming from GNOME or MATE?
If you're transitioning from GNOME or MATE, here are some key differences to understand:
- **Window Management**: In GNOME/MATE, windows can overlap freely and are typically manipulated with a mouse. In i3/I38, windows tile automatically and are primarily controlled with keyboard shortcuts.
- **Panels and Indicators**: Instead of persistent panels with menus and indicators, I38 uses keyboard shortcuts to access functionality.
- **Workspace Navigation**: While GNOME/MATE have workspaces that you can switch between, I38's workspaces are more central to the workflow and are accessed via dedicated keyboard shortcuts.
- **Application Launching**: Rather than using a start menu or activities overview, I38 provides keyboard shortcuts for launching applications.
I38 has been configured to make this transition easier by providing a tabbed layout (similar to browser tabs) and shortcuts that may feel somewhat familiar.
## Basic Concepts
### Workspaces
Workspaces act like virtual desktops, allowing you to organize applications. You have 10 workspaces available.
- Switch to workspace: `Control` + `F1` through `F10`
- Move window to workspace: `Control` + `Shift` + `F1` through `F10`
*GNOME/MATE comparison:* Similar to workspaces in GNOME/MATE, but with dedicated keyboard shortcuts rather than overview modes or workspace switchers.
### Window Management
Windows in I38 are arranged in a tabbed layout by default, which means windows take up the entire screen and you can switch between them like browser tabs.
- Switch between windows: `Alt` + `Tab` (next) or `Alt` + `Shift` + `Tab` (previous)
- Launch terminal: `MODKEY` + `Return`
- Close window: `MODKEY` + `F4`
- Toggle fullscreen: `MODKEY` + `BackSpace`
- List windows in current workspace: `RATPOISONKEY` then `'` (apostrophe)
*GNOME/MATE comparison:* Alt+Tab works similarly to GNOME/MATE, but window placement is automatic rather than manual.
## Modes in I38
### Default Mode
This is the standard mode for working with applications. Most commands start with your mod key (`MODKEY`).
### Ratpoison Mode
Ratpoison mode allows quick access to common actions using shorter key combinations. To enter Ratpoison mode, press `RATPOISONKEY`. After pressing this key, you can execute commands with single keystrokes.
Common Ratpoison mode commands:
| Key | Action |
|-----|--------|
| `c` | Launch a terminal |
| `e` | Open text editor (TEXTEDITOR) |
| `w` | Launch web browser (BROWSER) |
| `k` | Kill (close) the current window |
| `?` | Show I38 help |
| `Escape` or `Control` + `g` | Exit Ratpoison mode without taking action |
| `Shift` + `c` | Restart Cthulhu screen reader |
| `Shift` + `o` | Restart Orca screen reader |
| `Shift` + `t` | Toggle screen reader |
| `Control` + `;` | Reload I38 configuration |
| `Control` + `q` | Exit i3 (log out) |
| `!` | Open run dialog |
| `Alt` + `b` | Check battery status |
| `g` | Check game controller status |
*GNOME/MATE comparison:* This mode has no direct equivalent in GNOME/MATE. Think of it as a command palette or quick launcher activated by a single key.
### Bypass Mode
Bypass mode passes all keys directly to the application, which is useful for applications that need many keyboard shortcuts. To enter bypass mode, press `MODKEY` + `Shift` + `BackSpace`. Use the same key combination to exit bypass mode.
*GNOME/MATE comparison:* In GNOME/MATE, applications always receive keyboard input directly. Bypass mode simulates this behavior within i3.
## Accessibility Features
### Screen Reader
I38 is configured to work with your screen reader (SCREENREADER). The screen reader will provide spoken feedback about what's happening on screen so long as there is a window. If you don't have a window open and need to change something SCREENREADER related, press Control+Alt+d to bring up the desktop, then screen reader keys should work.
- Toggle screen reader: `RATPOISONKEY` then `Shift` + `t`
- Restart screen reader: `RATPOISONKEY` then `Shift` + `o` (for Orca) or `Shift` + `c` (for Cthulhu)
- Interrupt speech: `MODKEY` + `Shift` + `F5`
*GNOME/MATE comparison:* GNOME uses Orca by default with its own keyboard shortcuts. I38 integrates screen readers more deeply with the window manager.
### Braille Display Support
If you've enabled braille display support during setup, I38 will start XBrlAPI automatically to provide braille output from your screen reader.
### OCR (Optical Character Recognition)
If installed, you can use OCR to read text from images or inaccessible applications:
- `MODKEY` + `F5`: Perform OCR on the entire screen and speak the content
- In Ratpoison mode: `Print` or `MODKEY` + `r`: Perform OCR and save to clipboard
*GNOME/MATE comparison:* OCR features are typically not integrated into GNOME/MATE by default.
### Sound Effects
I38 provides audio feedback for many actions:
- Window open/close: Ascending/descending tones
- Mode changes: Distinctive sounds for each mode
- Workspace changes: Subtle audio cues
- Fullscreen toggle: Special sound effect
This audio feedback provides non-visual confirmation of actions and state changes.
*GNOME/MATE comparison:* GNOME/MATE typically have fewer sound effects for window management actions.
## Application Menu and Running Programs
Access applications in multiple ways:
- Applications menu: `MODKEY` + `F1`
- Run dialog (enter a command): `MODKEY` + `F2` or in Ratpoison mode, `!` (exclamation mark)
- Common applications have dedicated shortcuts in Ratpoison mode (see table above)
The applications menu is organized by categories similar to traditional desktop environments.
*GNOME/MATE comparison:* Instead of clicking on application icons or using a start menu, I38 provides keyboard shortcuts to access applications.
## Reminders and Notifications
I38 includes integration with the `remind` program for managing reminders:
- Access the reminder tool: `RATPOISONKEY` then `r`
- Create various types of reminders (one-time, daily, weekly, monthly)
- Get notification alerts for your reminders
The reminder tool provides the following features:
- **One-time Reminders**: Set for a specific date and time
- **Daily Reminders**: Occur every day at the specified time
- **Weekly Reminders**: Occur on specific days of the week
- **Monthly Reminders**: Occur on a specific day each month or the last day of each month
- **Custom Reminders**: Create complex reminder patterns
*GNOME/MATE comparison:* Similar to calendar applications in GNOME/MATE but with a simplified interface optimized for keyboard navigation.
## Volume and Media Controls
### System Volume
- Increase volume: `MODKEY` + `XF86AudioRaiseVolume`
- Decrease volume: `MODKEY` + `XF86AudioLowerVolume`
- Mute/unmute: `MODKEY` + `XF86AudioMute`
### Media Player Controls
- Play/Pause: `XF86AudioPlay`
- Next track: `XF86AudioNext`
- Previous track: `XF86AudioPrev`
- Stop: `XF86AudioStop`
- Media info: `MODKEY` + `XF86AudioPlay`
In Ratpoison mode, these are also available with Alt+Shift combinations:
- Increase volume: `Alt` + `Shift` + `=`
- Decrease volume: `Alt` + `Shift` + `-`
- Previous track: `Alt` + `Shift` + `z`
- Pause: `Alt` + `Shift` + `c`
- Play: `Alt` + `Shift` + `x`
- Stop: `Alt` + `Shift` + `v`
- Next track: `Alt` + `Shift` + `b`
- Media info: `Alt` + `Shift` + `u`
*GNOME/MATE comparison:* Media controls are similar to those in GNOME/MATE, with the addition of audio feedback.
## File Management
I38 uses FILEBROWSER for file management. Launch it in Ratpoison mode with the `f` key.
*GNOME/MATE comparison:* Similar functionality to Nautilus (GNOME) or Caja (MATE), but launched via keyboard shortcut rather than from a desktop icon or menu.
## System Operations
- Reload I38 configuration: In Ratpoison mode, `Control` + `;` (semicolon)
- Exit i3 (log out): In Ratpoison mode, `Control` + `q`
- Check battery status: In Ratpoison mode, `Alt` + `b`
- Check game controller status: In Ratpoison mode, `g`
- Adjust screen brightness (if xrandr is available): In Ratpoison mode, `Alt` + `s`
*GNOME/MATE comparison:* These functions are typically available through system menus or indicators in GNOME/MATE.
## Keyboard Layouts
Switch between layouts: `Super` + `Space`
This is only available if you chose multiple keyboard layouts during setup.
*GNOME/MATE comparison:* Similar to keyboard layout switching in GNOME/MATE, but with a different default shortcut.
## Desktop and Window Decorations
Unlike GNOME or MATE, i3 uses minimal window decorations. Windows don't have title bars with minimize/maximize buttons. Instead, windows fill their available space automatically, and interactions are performed through keyboard shortcuts.
- Show desktop icons: `MODKEY` + `Control` + `d`
## Clipboard Management
I38 includes clipboard management features:
- Access clipboard history: `MODKEY` + `Control` + `c`
## Bookmark Management
- Access bookmarks: `MODKEY` + `Control` + `b`
*GNOME/MATE comparison:* Bookmarks are typically managed within applications in GNOME/MATE. I38 provides a system-wide bookmark manager.
## Tips for New Users
- **Start with Ratpoison mode**: Learn the single-key commands first, as they're easier to remember.
- **Use the window list**: When you're lost, use `RATPOISONKEY` then `'` to show all windows in the current workspace.
- **Bookmark important websites**: Use `MODKEY` + `Control` + `b` to access bookmarks.
- **Remember the help shortcut**: `MODKEY` + `Shift` + `F1` is your friend when you need guidance.
- **Let the sound effects guide you**: Pay attention to the audio cues to understand what's happening.
- **Take advantage of OCR**: If an application isn't accessible, try the OCR function.
### Transitioning from GNOME/MATE
- Start by learning the basic navigation shortcuts before exploring advanced features
- The tabbed layout should feel somewhat familiar if you're used to browser tabs
- Alt+Tab works similarly to GNOME/MATE for switching between windows
- Focus on keyboard commands rather than looking for visual elements like panels or docks
## Customization
You can customize I38 by editing the file `~/.config/i3/customizations`. This file will not be overwritten when you update I38.
Example customizations:
```
# Change background color
exec_always --no-startup-id xsetroot -solid "#2E3440"
# Add custom keybinding
bindsym $mod+F12 exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ 100%
```
To reconfigure I38 completely, run the `i38.sh` script again.
*GNOME/MATE comparison:* Much more text-based configuration compared to the graphical settings dialogs in GNOME/MATE.
## Getting Help
If you need assistance with I38, you can:
- Press `MODKEY` + `Shift` + `F1` to view the help documentation
- Visit the Stormux website at [stormux.org](https://stormux.org)
- Check the i3 documentation at [i3wm.org/docs/userguide.html](https://i3wm.org/docs/userguide.html)
---
*I38 - Making i3 accessible. A Stormux project. License: GPL v3*

View File

@ -25,6 +25,7 @@ An uppercase I looks like a 1, 3 from i3, and 8 because the song [We Are 138](ht
- notification-daemon: To handle notifications - notification-daemon: To handle notifications
- xfce4-notifyd: For sending notifications Replaces notification-daemon (Sway user will need to install the customized variant at <https://github.com/icasdri/xfce4-notifyd-layer-shell>) - xfce4-notifyd: For sending notifications Replaces notification-daemon (Sway user will need to install the customized variant at <https://github.com/icasdri/xfce4-notifyd-layer-shell>)
- ocrdesktop: For getting contents of the current window with OCR. - ocrdesktop: For getting contents of the current window with OCR.
- pandoc or markdown: To generate html files.
- pamixer: for the mute-unmute script - pamixer: for the mute-unmute script
- pcmanfm: [optional] Graphical file manager. - pcmanfm: [optional] Graphical file manager.
- playerctl: music controls - playerctl: music controls

60
i38.sh
View File

@ -33,6 +33,11 @@ if [[ -n "${missing}" ]]; then
echo "${missing[*]}" echo "${missing[*]}"
exit 1 exit 1
fi fi
if ! command -v pandoc &> /dev/null && ! command -v markdown &> /dev/null ; then
echo "Please install either pandoc or markdown."
echo "The markdown command may be provided by the package discount."
exit 1
fi
keyboard_menu() { keyboard_menu() {
keyboardMenu=("us" "English (US)" keyboardMenu=("us" "English (US)"
@ -539,9 +544,6 @@ bindsym Control+F8 workspace number \$ws8, exec --no-startup-id ${i3Path}/script
bindsym Control+F9 workspace number \$ws9, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh bindsym Control+F9 workspace number \$ws9, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh
bindsym Control+F10 workspace number \$ws10, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh bindsym Control+F10 workspace number \$ws10, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh
# This is sort of a fake panel where some useful but seldom interacted with applications can live.
bindsym Control+Mod1+Tab workspace number 11, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh
# move focused container to workspace # move focused container to workspace
bindsym Control+Shift+F1 move container to workspace number \$ws1, exec spd-say -P important -Cw "moved to workspace 1" bindsym Control+Shift+F1 move container to workspace number \$ws1, exec spd-say -P important -Cw "moved to workspace 1"
bindsym Control+Shift+F2 move container to workspace number \$ws2, exec spd-say -P important -Cw "moved to workspace 2" bindsym Control+Shift+F2 move container to workspace number \$ws2, exec spd-say -P important -Cw "moved to workspace 2"
@ -578,6 +580,23 @@ if [[ ${#kbd[@]} -gt 1 ]]; then
echo "bindsym Mod4+space exec ${i3Path}/scripts/keyboard.sh cycle ${kbd[@]}" >> ${i3Path}/config echo "bindsym Mod4+space exec ${i3Path}/scripts/keyboard.sh cycle ${kbd[@]}" >> ${i3Path}/config
fi fi
# Create panel mode
cat << EOF >> ${i3Path}/config
# Panel mode configuration
bindsym Control+Mod1+Tab mode "panel"
mode "panel" {
# Weather information bound to w
bindsym w exec --no-startup-id ${i3Path}/scripts/weather.sh, mode "default"
# System information bound to s
bindsym s exec --no-startup-id ${i3Path}/scripts/sysinfo.sh, mode "default"
# Exit panel mode without any action
bindsym Escape mode "default"
bindsym Control+g mode "default"
}
EOF
# Create ratpoison mode if requested. # Create ratpoison mode if requested.
if [[ -n "${escapeKey}" ]]; then if [[ -n "${escapeKey}" ]]; then
cat << EOF >> ${i3Path}/config cat << EOF >> ${i3Path}/config
@ -641,7 +660,7 @@ bindsym Mod1+Shift+u exec --no-startup-id play -qV0 "| sox -np synth 0.03 sin 20
#Check battery status #Check battery status
bindsym Mod1+b exec --no-startup-id ${i3Path}/scripts/battery_status.sh, mode "default" bindsym Mod1+b exec --no-startup-id ${i3Path}/scripts/battery_status.sh, mode "default"
#Check controller battery status #Check controller battery status
bindsym g exec ${i3Path}/scripts/game_controler.sh -s, mode "default" bindsym g exec ${i3Path}/scripts/game_controller.sh -s, mode "default"
# Get a list of windows in the current workspace # Get a list of windows in the current workspace
bindsym apostrophe exec --no-startup-id ${i3Path}/scripts/window_list.sh, mode "default" bindsym apostrophe exec --no-startup-id ${i3Path}/scripts/window_list.sh, mode "default"
# Restart Cthulhu # Restart Cthulhu
@ -676,7 +695,7 @@ fi
cat << EOF >> ${i3Path}/config cat << EOF >> ${i3Path}/config
# Auto start section # Auto start section
$(if [[ $sounzzds -eq 0 ]]; then $(if [[ $sounds -eq 0 ]]; then
if [[ $usingSway -eq 0 ]]; then if [[ $usingSway -eq 0 ]]; then
echo "exec --no-startup-id ${i3Path}/scripts/sound.py" echo "exec --no-startup-id ${i3Path}/scripts/sound.py"
else else
@ -725,19 +744,34 @@ fi)
# First run help documentation # First run help documentation
exec --no-startup-id bash -c 'if [[ -f "${i3Path}/firstrun" ]]; then ${webBrowser} "${i3Path}/I38.html"& rm "${i3Path}/firstrun"; fi' exec --no-startup-id bash -c 'if [[ -f "${i3Path}/firstrun" ]]; then ${webBrowser} "${i3Path}/I38.html"& rm "${i3Path}/firstrun"; fi'
# Fake panel setup
exec --no-startup-id i3-msg 'workspace 11; workspace 1'
# If you want to add personal customizations to i3, add them in ${i3Path}/customizations # If you want to add personal customizations to i3, add them in ${i3Path}/customizations
# It is not overwritten when the config file is recreated. # It is not overwritten when the config file is recreated.
include "${i3Path}/customizations" include "${i3Path}/customizations"
# Applications that will be placed in workspace 11, which we use as a sort of fake panel.
include "${i3Path}/panel.conf"
EOF EOF
touch "${i3Path}/customizations" touch "${i3Path}/customizations"
cp -v panel.conf "${i3Path}/panel.conf" # Check for markdown or pandoc for converting the welcome document
# Move html help file to destination if command -v pandoc &> /dev/null ; then
cp -v I38.html "${i3Path}/I38.html" pandoc -f markdown -t html "I38.md" -so "${i3Path}/I38.html" --metadata title="Welcome to I38"
elif command -v markdown &> /dev/null ; then
cat << EOF > "${i3Path}/I38.html"
<!DOCTYPE html>
<html>
<head>
<title>Welcome to I38</title>
<meta charset="utf-8">
</head>
<body>
EOF
# Convert markdown to html and append to the file
markdown "I38.md" >> "${i3Path}/I38.html"
# Close the HTML tags using heredoc
cat << EOF >> "${i3Path}/I38.html"
</body>
</html>
EOF
fi
# More readable version of variables. # More readable version of variables.
escapeKey="${escapeKey//Mod1/Alt}" escapeKey="${escapeKey//Mod1/Alt}"

110
scripts/sysinfo.sh Executable file
View File

@ -0,0 +1,110 @@
#!/usr/bin/env bash
# Initialize variables
cpuUsage=0
cpuTemp=0
memoryUsed=0
memoryTotal=0
memoryPercent=0
swapUsed=0
swapTotal=0
swapPercent=0
diskUsed=0
diskTotal=0
diskPercent=0
networkSent=0
networkRecv=0
# Helper function for temperature conversion
celsius_to_fahrenheit() {
local celsius="$1"
[[ -z "$celsius" || "$celsius" == "--" ]] && echo "--" && return
[[ ! "$celsius" =~ ^-?[0-9]+(\.[0-9]+)?$ ]] && echo "--" && return
local fahrenheit
fahrenheit=$(echo "scale=1; ($celsius * 9/5) + 32" | bc -l)
echo "$fahrenheit"
}
update_system_data() {
# CPU usage
cpuUsage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
cpuUsage=$(printf "%.1f" "$cpuUsage" 2>/dev/null || echo "$cpuUsage")
# CPU temperature - fix for high readings
local tempCelsius="--"
if [[ -f /sys/class/thermal/thermal_zone0/temp ]]; then
tempCelsius=$(cat /sys/class/thermal/thermal_zone0/temp)
# Check if we need to divide by 1000 (common format)
if [[ $tempCelsius -gt 200 ]]; then
tempCelsius=$(echo "scale=1; $tempCelsius/1000" | bc -l)
fi
elif [[ -f /sys/class/hwmon/hwmon0/temp1_input ]]; then
tempCelsius=$(cat /sys/class/hwmon/hwmon0/temp1_input)
if [[ $tempCelsius -gt 200 ]]; then
tempCelsius=$(echo "scale=1; $tempCelsius/1000" | bc -l)
fi
elif command -v sensors &>/dev/null; then
tempCelsius=$(sensors | grep -oP 'Core 0.*?\+\K[0-9.]+')
fi
[[ "$tempCelsius" != "--" && "$tempCelsius" != "null" ]] && cpuTemp=$(celsius_to_fahrenheit "$tempCelsius") || cpuTemp="--"
# Memory usage
memoryTotal=$(free -m | awk '/^Mem:/{print $2/1024}')
memoryUsed=$(free -m | awk '/^Mem:/{print $3/1024}')
memoryPercent=$(free | awk '/^Mem:/{printf("%.1f", $3/$2 * 100)}')
# Swap usage
swapTotal=$(free -m | awk '/^Swap:/{print $2/1024}')
swapUsed=$(free -m | awk '/^Swap:/{print $3/1024}')
[[ "$swapTotal" -gt 0 ]] && swapPercent=$(free | awk '/^Swap:/{printf("%.1f", $3/$2 * 100)}') || swapPercent=0
# Disk usage
diskTotal=$(df -h / | awk 'NR==2 {print $2}')
diskUsed=$(df -h / | awk 'NR==2 {print $3}')
diskPercent=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')
# Network usage
networkSent=$(cat /proc/net/dev | grep -v "lo:" | awk '{s+=$10} END {printf "%.2f", s/1024/1024}')
networkRecv=$(cat /proc/net/dev | grep -v "lo:" | awk '{r+=$2} END {printf "%.2f", r/1024/1024}')
}
display_system_info() {
update_system_data
# Create the system information text with proper line breaks
systemInfoText="System Information
CPU
Usage: ${cpuUsage}%
Temperature: ${cpuTemp}° F
Memory
Usage: ${memoryUsed} / ${memoryTotal} GB (${memoryPercent}%)
Swap: ${swapUsed} / ${swapTotal} GB (${swapPercent}%)
Disk (Root Partition)
Usage: ${diskUsed} / ${diskTotal} GB (${diskPercent}%)
Network (Total Since Boot)
Received: ${networkRecv} MB
Sent: ${networkSent} MB
End of text. Press Control+Home to return to the beginning."
# Display in text-info dialog for screen reader accessibility
echo "$systemInfoText" | yad --pname=I38System \
--title="I38 System Information" \
--text-info \
--show-cursor \
--width=400 \
--height=400 \
--center \
--button="Close:0"
}
display_system_info
exit 0

439
scripts/weather.sh Executable file
View File

@ -0,0 +1,439 @@
#!/usr/bin/env bash
# Configuration settings
defaultCity="Raleigh, NC"
defaultLat="35.78"
defaultLon="-78.64"
tempUnit="F"
updateInterval=30
configDir="${XDG_CONFIG_HOME:-$HOME/.config}/stormux/I38"
configFile="$configDir/weather.conf"
mkdir -p "$configDir"
# Initialize variables
cityName="Detecting..."
latitude=0.0
longitude=0.0
currentTemp="--"
currentHumidity="--"
currentWindSpeed="--"
currentWindSpeedMph="--"
currentConditions="Unknown"
weatherLastUpdate=0
severeWeatherAlerted=0
declare -a forecastDates=("--" "--" "--")
declare -a forecastFormattedDates=("--" "--" "--")
declare -a forecastMinTemps=("--" "--" "--")
declare -a forecastMaxTemps=("--" "--" "--")
declare -a forecastConditions=("Unknown" "Unknown" "Unknown")
declare -A weatherCodes
weatherCodes[0]="Clear sky"
weatherCodes[1]="Mainly clear"
weatherCodes[2]="Partly cloudy"
weatherCodes[3]="Overcast"
weatherCodes[45]="Fog"
weatherCodes[48]="Rime fog"
weatherCodes[51]="Light drizzle"
weatherCodes[53]="Moderate drizzle"
weatherCodes[55]="Dense drizzle"
weatherCodes[56]="Light freezing drizzle"
weatherCodes[57]="Dense freezing drizzle"
weatherCodes[61]="Slight rain"
weatherCodes[63]="Moderate rain"
weatherCodes[65]="Heavy rain"
weatherCodes[66]="Light freezing rain"
weatherCodes[67]="Heavy freezing rain"
weatherCodes[71]="Slight snow fall"
weatherCodes[73]="Moderate snow fall"
weatherCodes[75]="Heavy snow fall"
weatherCodes[77]="Snow flurries"
weatherCodes[80]="Slight rain showers"
weatherCodes[81]="Moderate rain showers"
weatherCodes[82]="Heavy rain showers"
weatherCodes[85]="Slight snow showers"
weatherCodes[86]="Heavy snow showers"
weatherCodes[95]="Thunderstorm"
weatherCodes[96]="Thunderstorm with slight hail"
weatherCodes[99]="Thunderstorm with heavy hail"
declare -a severeWeatherCodes=(65 67 75 82 86 95 96 99)
# Button return codes
refreshBtn=0
quitBtn=1
settingsBtn=2
trap "pkill -P $$" EXIT INT TERM
# Load configuration if available
if [ -f "$configFile" ]; then
source "$configFile"
# Convert lastWeatherUpdate string to integer if it exists
[[ -n "$lastWeatherUpdate" ]] && weatherLastUpdate=$lastWeatherUpdate || weatherLastUpdate=0
if [[ -n "$city" ]]; then
cityName="$city"
latitude="$latitude"
longitude="$longitude"
fi
# Try to reload saved weather data
if [[ "$weatherLastUpdate" -gt 0 && "$currentTemp" == "--" ]]; then
[[ -n "$savedCurrentTemp" ]] && currentTemp="$savedCurrentTemp"
[[ -n "$savedCurrentHumidity" ]] && currentHumidity="$savedCurrentHumidity"
[[ -n "$savedCurrentConditions" ]] && currentConditions="$savedCurrentConditions"
[[ -n "$savedCurrentWindSpeed" ]] && currentWindSpeedMph="$savedCurrentWindSpeed"
for i in {0..2}; do
varDate="savedForecastDate_$i"
varMin="savedForecastMin_$i"
varMax="savedForecastMax_$i"
varCond="savedForecastCond_$i"
[[ -n "${!varDate}" ]] && forecastFormattedDates[$i]="${!varDate}"
[[ -n "${!varMin}" ]] && forecastMinTemps[$i]="${!varMin}"
[[ -n "${!varMax}" ]] && forecastMaxTemps[$i]="${!varMax}"
[[ -n "${!varCond}" ]] && forecastConditions[$i]="${!varCond}"
done
fi
fi
# Helper functions
time_diff() {
local timestamp="$1"
local now=$(date +%s)
local diff=$((now - timestamp))
if [ $diff -lt 60 ]; then
echo "just now"
elif [ $diff -lt 3600 ]; then
local minutes=$((diff / 60))
echo "$minutes minute$([ $minutes -ne 1 ] && echo "s") ago"
elif [ $diff -lt 86400 ]; then
local hours=$((diff / 3600))
echo "$hours hour$([ $hours -ne 1 ] && echo "s") ago"
else
local days=$((diff / 86400))
echo "$days day$([ $days -ne 1 ] && echo "s") ago"
fi
}
celsius_to_fahrenheit() {
local celsius="$1"
[[ -z "$celsius" || "$celsius" == "--" ]] && echo "--" && return
[[ ! "$celsius" =~ ^-?[0-9]+(\.[0-9]+)?$ ]] && echo "--" && return
local fahrenheit
fahrenheit=$(echo "scale=1; ($celsius * 9/5) + 32" | bc -l)
echo "$fahrenheit"
}
kmh_to_mph() {
local kmh="$1"
[[ -z "$kmh" || "$kmh" == "--" ]] && echo "--" && return
[[ ! "$kmh" =~ ^-?[0-9]+(\.[0-9]+)?$ ]] && echo "--" && return
local mph
mph=$(echo "scale=1; $kmh * 0.621371" | bc -l)
echo "$mph"
}
format_date() {
local isoDate="$1"
[[ -z "$isoDate" || "$isoDate" == "--" ]] && echo "--" && return
date -d "$isoDate" "+%A, %B %d" 2>/dev/null || echo "$isoDate"
}
# Save configuration
save_config() {
cat > "$configFile" << EOF
city="$cityName"
latitude="$latitude"
longitude="$longitude"
tempUnit=$tempUnit
updateInterval=$updateInterval
lastWeatherUpdate=$weatherLastUpdate
savedCurrentTemp="$currentTemp"
savedCurrentHumidity="$currentHumidity"
savedCurrentConditions="$currentConditions"
savedCurrentWindSpeed="$currentWindSpeedMph"
savedForecastDate_0="${forecastFormattedDates[0]}"
savedForecastMin_0="${forecastMinTemps[0]}"
savedForecastMax_0="${forecastMaxTemps[0]}"
savedForecastCond_0="${forecastConditions[0]}"
savedForecastDate_1="${forecastFormattedDates[1]}"
savedForecastMin_1="${forecastMinTemps[1]}"
savedForecastMax_1="${forecastMaxTemps[1]}"
savedForecastCond_1="${forecastConditions[1]}"
savedForecastDate_2="${forecastFormattedDates[2]}"
savedForecastMin_2="${forecastMinTemps[2]}"
savedForecastMax_2="${forecastMaxTemps[2]}"
savedForecastCond_2="${forecastConditions[2]}"
EOF
}
# Play severe weather alert sound using Sox
play_severe_weather_alert() {
if command -v play &>/dev/null; then
# Generate alert sound pattern using sox
play -nqV0 synth 2 sine 853 sine 960 remix - norm -15 &
fi
# Also display notification if available
if command -v notify-send &>/dev/null; then
notify-send "Severe Weather Alert" "Severe weather conditions detected for $cityName: $currentConditions" -u critical
fi
}
# Function to check if a value is in array
in_array() {
local value="$1"
shift
local array=("$@")
for item in "${array[@]}"; do
if [[ "$item" == "$value" ]]; then
return 0 # True, found in array
fi
done
return 1 # False, not found
}
# Function to detect location
get_location() {
# Only try location detection if we don't already have a city name
if [[ "$cityName" == "Detecting..." ]]; then
echo "Attempting to detect location via ipinfo.io..."
# Try to fetch location data
local locationData
locationData=$(curl -s --connect-timeout 5 "https://ipinfo.io/json" 2>/dev/null)
if [[ $? -eq 0 && -n "$locationData" && $(echo "$locationData" | jq -e '.city') ]]; then
echo "Location data received successfully"
cityName=$(echo "$locationData" | jq -r '.city // "Unknown"')
local region=$(echo "$locationData" | jq -r '.region // ""')
# Add region/state to city name if available
[[ -n "$region" ]] && cityName="$cityName, $region"
# Extract coordinates directly from the "loc" field
local loc=$(echo "$locationData" | jq -r '.loc // "0,0"')
latitude=$(echo "$loc" | cut -d',' -f1)
longitude=$(echo "$loc" | cut -d',' -f2)
save_config
else
cityName="$defaultCity"
latitude="$defaultLat"
longitude="$defaultLon"
save_config
fi
fi
}
# Function to fetch weather data
fetch_weather_data() {
local now=$(date +%s)
local elapsedMinutes=$(( (now - weatherLastUpdate) / 60 ))
# Only fetch if needed
if [[ $weatherLastUpdate -eq 0 || $elapsedMinutes -ge $updateInterval ]]; then
local url="https://api.open-meteo.com/v1/forecast?latitude=$latitude&longitude=$longitude&current=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m&daily=weather_code,temperature_2m_max,temperature_2m_min&timezone=auto"
local response=$(curl -s --connect-timeout 10 "$url" 2>/dev/null)
if [[ $? -eq 0 && -n "$response" && $(echo "$response" | jq -e '.current' 2>/dev/null) ]]; then
# Update current weather data
local tempCelsius=$(echo "$response" | jq -r '.current.temperature_2m // "--"' 2>/dev/null)
[[ "$tempCelsius" != "--" && "$tempCelsius" != "null" ]] && currentTemp=$(celsius_to_fahrenheit "$tempCelsius") || currentTemp="--"
currentHumidity=$(echo "$response" | jq -r '.current.relative_humidity_2m // "--"' 2>/dev/null)
[[ "$currentHumidity" == "null" ]] && currentHumidity="--"
currentWindSpeed=$(echo "$response" | jq -r '.current.wind_speed_10m // "--"' 2>/dev/null)
if [[ "$currentWindSpeed" != "--" && "$currentWindSpeed" != "null" ]]; then
currentWindSpeedMph=$(kmh_to_mph "$currentWindSpeed")
else
currentWindSpeed="--"
currentWindSpeedMph="--"
fi
local weatherCode=$(echo "$response" | jq -r '.current.weather_code // 0' 2>/dev/null)
[[ "$weatherCode" == "null" ]] && weatherCode=0
currentConditions="${weatherCodes[$weatherCode]:-Unknown}"
# Check for severe weather and play alert if needed
if in_array "$weatherCode" "${severeWeatherCodes[@]}"; then
if [ "$severeWeatherAlerted" -eq 0 ]; then
play_severe_weather_alert
severeWeatherAlerted=1
fi
else
# Reset alert flag if weather is no longer severe
severeWeatherAlerted=0
fi
# Process forecast data (limited to 3 days)
if [[ $(echo "$response" | jq -e '.daily' 2>/dev/null) ]]; then
for i in {0..2}; do
# Process forecast data
forecastDates[$i]=$(echo "$response" | jq -r ".daily.time[$i] // \"--\"" 2>/dev/null)
[[ "${forecastDates[$i]}" != "--" && "${forecastDates[$i]}" != "null" ]] && \
forecastFormattedDates[$i]=$(format_date "${forecastDates[$i]}") || forecastFormattedDates[$i]="--"
local minTempC=$(echo "$response" | jq -r ".daily.temperature_2m_min[$i] // \"--\"" 2>/dev/null)
[[ "$minTempC" != "--" && "$minTempC" != "null" ]] && \
forecastMinTemps[$i]=$(celsius_to_fahrenheit "$minTempC") || forecastMinTemps[$i]="--"
local maxTempC=$(echo "$response" | jq -r ".daily.temperature_2m_max[$i] // \"--\"" 2>/dev/null)
[[ "$maxTempC" != "--" && "$maxTempC" != "null" ]] && \
forecastMaxTemps[$i]=$(celsius_to_fahrenheit "$maxTempC") || forecastMaxTemps[$i]="--"
local code=$(echo "$response" | jq -r ".daily.weather_code[$i] // 0" 2>/dev/null)
[[ "$code" == "null" ]] && code=0
forecastConditions[$i]="${weatherCodes[$code]:-Unknown}"
done
fi
# Update timestamp
weatherLastUpdate=$(date +%s)
save_config
else
echo "Failed to fetch weather data. Response code: $?"
if [[ -n "$response" ]]; then
echo "First 100 chars of response: ${response:0:100}"
fi
fi
fi
}
# Function to change location (for settings)
change_location() {
local newLocation="$1"
if [[ -n "$newLocation" && "$newLocation" != "$cityName" ]]; then
# Try to parse the location using curl to a geocoding service
local result=$(curl -s --connect-timeout 10 "https://nominatim.openstreetmap.org/search?q=$newLocation&format=json" 2>/dev/null)
if [[ -n "$result" && $(echo "$result" | jq -e '.[0]') ]]; then
cityName="$newLocation"
latitude=$(echo "$result" | jq -r '.[0].lat // "0.0"')
longitude=$(echo "$result" | jq -r '.[0].lon // "0.0"')
# Force weather update
weatherLastUpdate=0
save_config
return 0
else
yad --title "Location Error" --text="Could not find location: $newLocation" --button=gtk-ok
return 1
fi
fi
return 1
}
# Display weather information in a text-info dialog
display_weather() {
local lastUpdateText="Never updated"
[[ "$weatherLastUpdate" -gt 0 ]] && lastUpdateText="Last updated: $(time_diff "$weatherLastUpdate")"
# Create the weather information text with proper line breaks
weatherInfoText="Weather for $cityName
$lastUpdateText
Current Conditions
Temperature: ${currentTemp}° F
Conditions: $currentConditions
Humidity: ${currentHumidity}%
Wind Speed: ${currentWindSpeedMph} mph
3-Day Forecast
────────────────────────────────────
${forecastFormattedDates[0]}
Temp: ${forecastMinTemps[0]}° to ${forecastMaxTemps[0]}° F
Conditions: ${forecastConditions[0]}
────────────────────────────────────
${forecastFormattedDates[1]}
Temp: ${forecastMinTemps[1]}° to ${forecastMaxTemps[1]}° F
Conditions: ${forecastConditions[1]}
────────────────────────────────────
${forecastFormattedDates[2]}
Temp: ${forecastMinTemps[2]}° to ${forecastMaxTemps[2]}° F
Conditions: ${forecastConditions[2]}
End of text. Press Control+Home to return to the beginning."
# Display in text-info dialog for screen reader accessibility
echo "$weatherInfoText" | yad --pname=I38Weather \
--title="I38 Weather Monitor" \
--text-info \
--show-cursor \
--width=500 \
--height=600 \
--center \
--button="Settings:$settingsBtn" \
--button="Refresh:$refreshBtn" \
--button="Close:$quitBtn"
return $?
}
# Function to display settings dialog
display_settings() {
local ret=$(yad --pname=I38WeatherSettings \
--title="I38 Weather Settings" \
--form \
--width=400 \
--center \
--field="Location:":TEXT "$cityName" \
--field="Current Coordinates:":LBL "Lat: $latitude, Lon: $longitude" \
--field="Temperature Unit:":CB "F!C" \
--field="Update Interval (minutes):":NUM "$updateInterval!5..120!5" \
--button="Cancel:1" \
--button="Save:0")
local saveResult=$?
if [[ $saveResult -eq 0 && -n "$ret" ]]; then
local newLocation=$(echo "$ret" | cut -d"|" -f1)
local newUnit=$(echo "$ret" | cut -d"|" -f3)
local newInterval=$(echo "$ret" | cut -d"|" -f4)
# Apply any changes
[[ -n "$newLocation" && "$newLocation" != "$cityName" ]] && change_location "$newLocation"
[[ -n "$newUnit" && "$newUnit" != "$tempUnit" ]] && tempUnit="$newUnit" && save_config
[[ -n "$newInterval" && "$newInterval" != "$updateInterval" ]] && updateInterval="$newInterval" && save_config
fi
}
# Main loop
while : ; do
get_location
fetch_weather_data
# Display weather using the text-info widget
display_weather
ret=$?
# Handle button actions
case $ret in
$refreshBtn)
# Force a weather update
weatherLastUpdate=0
continue
;;
$settingsBtn)
# Display settings dialog
display_settings
continue
;;
$quitBtn|252)
# Quit button or window closed
break
;;
esac
done
exit 0