justDeeevin / NuhxBoard

A cross-platform alternative to NohBoard

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

The NuhxBoard logo NuhxBoard

Goals

Nohboard is great! But it’s only for Windows. The only alternative is Tiyenti’s KBDisplay, which is quite nice, but limited in functionality. My primary goal with this project is to replicate the functionality of NohBoard in a cross-compatible manner. More specifically, I want to be able to feed in any NohBoard config file and have near-identical output to NohBoard.

I may add functionality where I think it would fit, but I want to prioritize interoperability with NohBoard. Call it just another incentive for gamers to switch to Linux.

Usage

NuhxBoard is made with customizability in mind. Every part of its appearance and behavior is configurable. At its core, NuhxBoard is an app that loads keyboard layouts and styles. A keyboard layout defines the positions, shapes, and behaviors of keys. It also defines the dimensions of the window. A style defines colors, fonts, and (in a future release) images for keys.

Keyboard layouts are grouped into categories, and styles (aside from global ones) correspond to specific keyboard layouts.

Keyboards are located in ~/.local/share/NuhxBoard/keyboards. Here’s the general structure of that directory:

  • keyboards/

    • [CATEGORY NAME]/

      • images/

      • [KEYBOARD NAME]/

        • keyboard.json

        • [STYLE NAME].style

      • global/

        • [STYLE NAME].style

This folder will be populated on first run with some example keyboards and categories. You can inspect it yourself to get a good idea of how this looks in practice.

To load a keyboard and style, right-click anywhere in NuhxBoard to open the global context menu and click on "Load Keyboard". This will open a new window. The drop-down list labeled "Categories" allows you to select a category. When a category has been selected, the keyboards available in that category will appear in a list on the left side of the vertical line. When you click on one of these options, your selection of keyboard layout will be loaded, and that keyboard layout’s available styles will appear in a list on the right side of the vertical line. When you click on one of these options, your selection of style will be loaded. You can change your selection of keyboard layout and style at any time through this interface.

Keyboard Layouts

As previously stated, keyboard layouts define key positions, shapes, and behaviors, as well as window dimensions. Keyboard layouts are defined by a JSON file, keyboard.json, in their corresponding named directory. Here’s what the type definition for a keyboard layout looks like in rust:

#[derive(Serialize, Deserialize, Default, Debug)]
pub struct Config {
    #[serde(rename = "Version")]
    pub version: Option<u8>,
    #[serde(rename = "Width")]
    pub width: f32,
    #[serde(rename = "Height")]
    pub height: f32,
    #[serde(rename = "Elements")]
    pub elements: Vec<BoardElement>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "__type")]
pub enum BoardElement {
    KeyboardKey(KeyboardKeyDefinition),
    MouseKey(MouseKeyDefinition),
    MouseScroll(MouseScrollDefinition),
    MouseSpeedIndicator(MouseSpeedIndicatorDefinition),
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct KeyboardKeyDefinition {
    #[serde(rename = "Id")]
    pub id: u32,
    #[serde(rename = "Boundaries")]
    pub boundaries: Vec<SerializablePoint>,
    #[serde(rename = "TextPosition")]
    pub text_position: SerializablePoint,
    #[serde(rename = "KeyCodes")]
    pub keycodes: Vec<u32>,
    #[serde(rename = "Text")]
    pub text: String,
    #[serde(rename = "ShiftText")]
    pub shift_text: String,
    #[serde(rename = "ChangeOnCaps")]
    pub change_on_caps: bool,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MouseKeyDefinition {
    #[serde(rename = "Id")]
    pub id: u32,
    #[serde(rename = "Boundaries")]
    pub boundaries: Vec<SerializablePoint>,
    #[serde(rename = "TextPosition")]
    pub text_position: SerializablePoint,
    #[serde(rename = "KeyCodes")]
    pub keycodes: Vec<u32>,
    #[serde(rename = "Text")]
    pub text: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MouseScrollDefinition {
    #[serde(rename = "Id")]
    pub id: u32,
    #[serde(rename = "Boundaries")]
    pub boundaries: Vec<SerializablePoint>,
    #[serde(rename = "TextPosition")]
    pub text_position: SerializablePoint,
    #[serde(rename = "KeyCodes")]
    pub keycodes: Vec<u32>,
    #[serde(rename = "Text")]
    pub text: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MouseSpeedIndicatorDefinition {
    #[serde(rename = "Id")]
    pub id: u32,
    #[serde(rename = "Location")]
    pub location: SerializablePoint,
    #[serde(rename = "Radius")]
    pub radius: f32,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SerializablePoint {
    #[serde(rename = "X")]
    pub x: f32,
    #[serde(rename = "Y")]
    pub y: f32,
}

If you can make sense of that, then good for you! Otherwise, here’s an actual explanation of how a keyboard layout is defined.

Top-Level Properties

All points are represented as an object with an X and Y property.

Version

No actual meaning. Kept for parity with NohBoard layout files.

Width

Width of the window in pixels.

Height

Height of the window in pixels.

Elements

Array of elements in the layout.

Elements

There are four kinds of elements: KeyboardKeys, MouseKeys, MouseScrolls, and MouseSpeedIndicators. Each item in the list of elements indicates what kind it is by having a __type property.


Shared Properties

These properties are shared by KeyboardKeys, MouseKeys, and MouseScrolls.

Id

Each element has a unique Id. Style files can apply styles to specific keys by referring to their Id.

Boundaries

Elements' shapes are defined by an array of points, their vertices. When no image is specified for an element, it is drawn by connecting lines between each point in the order they appear in the list (including closing the shape by connecting the last vertex to the first), then filling the polygon formed. Even if an element has an image specified, the boundaries are used for the graphical layout editor to know when your cursor is hovering over an element.

TextPosition

The point where the top-left corner of the element’s text is to be. Technically, this can be anywhere in the window.

KeyCodes

An array containing the keycodes (just integers) this key should track. You can have one element listen for multiple keys! In a future release, there will be a tool in the element properties menu of the graphical layout editor that will help to figure out which key corresponds to which keycode. For the time being, you can check this document for conversion.

Text

The text to display on the key.


KeyboardKey

In addition to the shared properties, KeyboardKeys have the following properties:

ShiftText

The text to display when shift is held.

ChangeOnCaps

Whether or not to follow the state of caps lock (generally, this is true for letters and false for symbols).


MouseSpeedIndicator

MouseSpeedIndicators are drawn differently, behave differently, and thus are defined differently. They have IDs, but none of the other shared properties.

MouseSpeedIndicators are made up of a filled inner circle and an unfilled outer ring. There is a triangle extending to a point along the outer ring. The direction of the triangle indicates the direction of the velocity of the mouse, and the closness of the triangle’s end to the outer ring indicates the magnitude.

MouseSpeedIndicator example

Location

The center of the circle

Radius

The radius of the outer ring. The inner ring is 20% of this radius.


Styles

Styles describe colors, fonts, and (in a later release) images with which to display a keyboard layout. Proper styling is crucial to making a good keyboard layout.

Again, here’s the type definition in rust:

#[derive(Serialize, Deserialize, Debug)]
pub struct Style {
    #[serde(rename = "BackgroundColor")]
    pub background_color: NohRgb,
    #[serde(rename = "BackgroundImageFileName")]
    pub background_image_file_name: Option<String>,
    #[serde(rename = "DefaultKeyStyle")]
    pub default_key_style: KeyStyle,
    #[serde(rename = "DefaultMouseSpeedIndicatorStyle")]
    pub default_mouse_speed_indicator_style: MouseSpeedIndicatorStyle,
    #[serde(rename = "ElementStyles")]
    pub element_styles: Vec<ElementStyle>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NohRgb {
    #[serde(rename = "Red")]
    pub red: f32,
    #[serde(rename = "Green")]
    pub green: f32,
    #[serde(rename = "Blue")]
    pub blue: f32,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct KeyStyle {
    #[serde(rename = "Loose")]
    pub loose: Option<KeySubStyle>,
    #[serde(rename = "Pressed")]
    pub pressed: Option<KeySubStyle>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct KeySubStyle {
    #[serde(rename = "Background")]
    pub background: NohRgb,
    #[serde(rename = "Text")]
    pub text: NohRgb,
    #[serde(rename = "Outline")]
    pub outline: NohRgb,
    #[serde(rename = "ShowOutline")]
    pub show_outline: bool,
    #[serde(rename = "OutlineWidth")]
    pub outline_width: u32,
    #[serde(rename = "Font")]
    pub font: Font,
    #[serde(rename = "BackgroundImageFileName")]
    pub background_image_file_name: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Font {
    #[serde(rename = "FontFamily")]
    pub font_family: String,
    #[serde(rename = "Size")]
    pub size: f32,
    #[serde(rename = "Style")]
    pub style: u8,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct MouseSpeedIndicatorStyle {
    #[serde(rename = "InnerColor")]
    pub inner_color: NohRgb,
    #[serde(rename = "OuterColor")]
    pub outer_color: NohRgb,
    #[serde(rename = "OutlineWidth")]
    pub outline_width: f32,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ElementStyle {
    #[serde(rename = "Key")]
    pub key: u32,
    #[serde(rename = "Value")]
    pub value: ElementStyleUnion,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "__type")]
pub enum ElementStyleUnion {
    KeyStyle(KeyStyle),
    MouseSpeedIndicatorStyle(MouseSpeedIndicatorStyle),
}

Top-Level Properties

All images are stored in the images directory in the category. Images are refferred to by name, including the file extension.

All colors are represented as an object with three properties: Red, Green, and Blue. Each is an integer between 0 and 255.

BackgroundColor

The color of the background. Will be overriden by a background image if one is specified.

BackgroundImageFileName

The name of the image file to use as the background. This is optional.

DefaultKeyStyle

The default style to use for all "keys" (every element besides MouseSpeedIndicators). This must be spicified.

DefaultMouseSpeedIndicatorStyle

The default style to use for all MouseSpeedIndicators. This must be specified.

ElementStyles

An array of ElementStyle objects. Each ElementStyle object has a Key property, which is the Id of the element to which the style should be applied, and a Value property, which is either a KeyStyle or a MouseSpeedIndicatorStyle. Again, each item indicates its type with the __type property.


KeyStyle

KeyStyles just list which style to use for when a key is Pressed or Loose (not pressed). The actual style is defined in the KeySubStyle object, with these properties:

Background

The color of the key.

Text

The color of the text on the key.

Outline

The color of the outline around the key.

ShowOutline

Whether or not to draw an outline around the key.

OutlineWidth

The width of the outline in pixels.

Font

The font to use for the text on the key. See Fonts for more information.

BackgroundImageFileName

This is unimplemented.

The name of the image file to use as the background of the key.


MouseSpeedIndicatorStyle

InnerColor

The color of the filled inner circle.

OuterColor

The color of the outer ring.

OutlineWidth

The width of the outer ring.


Fonts

FontFamily

The name of the font to use. This is the name of the font as it appears in the system’s font list.

Size

The size of the font in pixels.

Style

A bitfield representing the style of the font. From least to most significant, the first bit is bold, the second italic, the third underline, and the fourth strikethrough. These effects can be combined. As an example, if I wanted bold and italicized text, I would set style to 3, which is 0011 in binary.


Settings

In the global context menu, there is a "Settings" button, which opens a window with the following options:

Automatically create a desktop entry if none exists
Mouse sensitivity

The sensitivity of the MouseSpeedIndicator.

Scroll hold time (ms)

Time in milliseconts to highlight a mouse scroll key when a scroll is detected.

Calculate mouse speed from center of screen

Some games recenter the mouse every frame. If you find that you’re looking around ingame but the MouseSpeedIndicator is behaving strangely, try turning this option on.

Display to use

The ID of the display to use for the above option. The primary monitor is marked as such, but if you have many monitors, you’ll probably have to use trial and error to determine which is which.

Show keypresses for at least _ ms

If you press and immediately release a key, it will stay highlighted for this many milliseconds. If you hold a key down, it will stay highlighted for this many milliseconds after its release.

Window title
Follow Caps-Lock and Shift

These three radio buttons allow you to fine-tune capitalization behavior. With the last two options selected, caps lock will be ignored everywhere, and instead all keys will either be capitalized or lowercase depending upon your selection. The two checkboxes to the right allow you to still follow shift for certain keys. Think of this as allowing you to force caps lock to be either on or off for all keys. For instance, when NuhxBoard is configured to show all buttons capitalized but still follow shift for all keys, when shift is held, all keys will be lowercase, similar to if shift were held while caps lock was followed and enabled.

Caveats

On Windows and Linux under Wayland, NuhxBoard can’t capture input while focused. This can’t be fixed right now, but a future release of the UI library will allow it to be. On Linux under Walyand, this is actually a symptom of a larger issue. Wayland provides no method of globally listening to input events. NuhxBoard still works most of the time because of XWayland, but when a Wayland-native app (such as NuhxBoard itself) has focus, NuhxBoard stops working. This shouldn’t be a real dealbreaker for a while, though, since Wayland-native apps are still relavively few and far between, especially with games.

Installation

NuhxBoard is currently only on crates.io. It can also be installed with Cargo Binstall. You can also install NuhxBoard using the option matching your platform on the latest release page.

NuhxBoard will detect if any app files are missing and replace them automatically. This includes

The main settings

If the NuhxBoard.json file containing app settings and saved state doesn’t exist, it’ll be populated with defaults.

Installed keyboards

If the keyboards directory is empty or doesn’t exist, then nuhxboard will download a pack of example keyboards to use.

Optionally, desktop entries

On windows, if there isn’t a listing in the start menu, or on Linux, if there isn’t a desktop entry in the proper place, NuhxBoard will create one. This behavior can be disabled in case you want to make your own desktop entry.

Demo

NuhxBoard.Demo.mp4

Configurable like NohBoard:

NuhxBoard.settings.demo.mp4

About

A cross-platform alternative to NohBoard

License:GNU General Public License v3.0


Languages

Language:Rust 97.3%Language:Nix 2.6%Language:Just 0.1%