Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions lockscreens/nothing-interface/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Ivan

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.
18 changes: 18 additions & 0 deletions lockscreens/nothing-interface/PROVENANCE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Nothing Interface qylock theme

Original QML and helper scripts by Ivan, 2026, under the MIT License in this
folder. The design is an independent fan interpretation of Nothing OS.

No Nothing wallpaper, proprietary font, logo or AI-generated wallpaper is
installed with this lockscreen. During an in-session lock, the theme reads the
current Ryogami wallpaper, so it changes together with the desktop wallpaper.
It shows the local battery state, Ryoku's daily screen-time counter, and MPRIS
media metadata. The media control uses playerctl. In the system greeter, live
session-only data is unavailable and the background falls back to charcoal.

The catalogue preview is a capture of the interface, not part of the installed
theme. The wallpaper and album art visible in that capture are not redistributed
as standalone assets.

Upstream and maintainer: https://github.com/venchik111/ryoku-nothing-interface
Nothing trademarks and the referenced visual identity remain with their owners.
18 changes: 18 additions & 0 deletions lockscreens/nothing-interface/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Nothing Interface lockscreen

A live Ryoku lockscreen with dot clock, date, battery, daily screen time and
MPRIS media controls. In a locked desktop session it displays the current
Ryogami wallpaper and follows wallpaper changes. It does not install its own
wallpaper. The system login greeter uses a charcoal fallback and cannot show
the session's live data.

The QML and Python helpers are original MIT-licensed code by Ivan. Python 3 and
`playerctl` are used for live media data; the helpers read local power-supply
state and Ryoku's screen-time state. They write no files, make no outbound
network requests, and require no privileged commands. The image URL provided by
a local media player may be fetched by Qt when the lockscreen shows album art.

The catalogue preview is not installed. No Nothing artwork or font is bundled.
See [PROVENANCE.txt](PROVENANCE.txt) and the
[upstream project](https://github.com/venchik111/ryoku-nothing-interface).
Maintainer: [venchik111](https://github.com/venchik111).
Binary file added lockscreens/nothing-interface/assets/preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
92 changes: 92 additions & 0 deletions lockscreens/nothing-interface/content/LiveData.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import QtQuick
import Quickshell
import Quickshell.Io

Item {
id: root
readonly property string themeDir: Quickshell.env("QS_THEME_PATH") || ""
readonly property string socketPath: (Quickshell.env("XDG_RUNTIME_DIR") || "/tmp") + "/ryogami.sock"
property string wallpaperPath: ""
property string videoPath: ""
property int wallpaperRevision: 0
property string fit: "Cover"
readonly property string wallpaperUrl: wallpaperPath.length ? "file://" + wallpaperPath + "?v=" + wallpaperRevision : ""
readonly property string videoUrl: videoPath.length ? "file://" + videoPath : ""
property var battery: ({level: -1, charging: false, plugged: false})
property var activity: ({seconds: 0, available: false})
property var media: ({playing: false, available: false, title: "", artist: "", position: 0, length: 0})

function applyWallpaper(line) {
try {
const frame = JSON.parse(line)
const output = Screen.name || ""
const entry = (frame.outputs && frame.outputs[output]) || frame.default
if (!entry) return
wallpaperPath = entry.path || ""
videoPath = entry.videoPath || ""
wallpaperRevision = Number(entry.revision || 0)
fit = entry.fit || "Cover"
} catch (error) {
console.warn("nothing-interface: invalid wallpaper frame", error)
}
}

function readJson(raw, target) {
if (!raw || !raw.trim()) return
try { root[target] = JSON.parse(raw) }
catch (error) { console.warn("nothing-interface: invalid " + target + " data", error) }
}
function refresh() {
if (!batteryProbe.running) batteryProbe.running = true
if (!activityProbe.running) activityProbe.running = true
if (!mediaProbe.running) mediaProbe.running = true
}

Socket {
id: wallpaperSocket
path: root.socketPath
parser: SplitParser { onRead: line => root.applyWallpaper(line) }
Component.onCompleted: connected = true
onConnectionStateChanged: {
if (connected) {
write("subscribe wallpaper\n")
flush()
} else retry.restart()
}
onError: {
connected = false
retry.restart()
}
}
Timer {
id: retry
interval: 2000
onTriggered: if (!wallpaperSocket.connected) wallpaperSocket.connected = true
}
Process {
id: batteryProbe
command: ["python3", root.themeDir + "/bin/status.py"]
stdout: StdioCollector { onStreamFinished: root.readJson(this.text, "battery") }
}
Process {
id: activityProbe
command: ["python3", root.themeDir + "/bin/activity.py"]
stdout: StdioCollector { onStreamFinished: root.readJson(this.text, "activity") }
}
Process {
id: mediaProbe
command: ["python3", root.themeDir + "/bin/media.py"]
stdout: StdioCollector { onStreamFinished: root.readJson(this.text, "media") }
}
Process {
id: playback
command: ["playerctl", "play-pause"]
onExited: mediaProbe.running = true
}
function togglePlayback() {
if (media.available && !playback.running) playback.running = true
}
Timer { interval: 10000; repeat: true; running: true; onTriggered: root.refresh() }
Timer { interval: 2000; repeat: true; running: true; onTriggered: if (!mediaProbe.running) mediaProbe.running = true }
Component.onCompleted: Qt.callLater(refresh)
}
20 changes: 20 additions & 0 deletions lockscreens/nothing-interface/content/LiveVideo.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import QtQuick
import QtMultimedia

Item {
id: root
property url sourceUrl: ""
VideoOutput {
id: output
anchors.fill: parent
fillMode: VideoOutput.PreserveAspectCrop
}
MediaPlayer {
id: player
source: root.sourceUrl
videoOutput: output
audioOutput: AudioOutput { muted: true }
loops: MediaPlayer.Infinite
onSourceChanged: if (source.toString().length) play()
}
}
Loading