Add iio-lock plugin for orientation locking (#13)

* Made a iio-hyprland plugin for noctalia
kill iio-hyprland and restart it depending on the locked setting.

The plugin has:
-control center toggle
-Bar wigdet for toggle of state
--If rigth clicked this wigdet will open the panel
-Panel for calling by msg
--Panel also added possibally to specify lock direction
-Bar wiget for open panel
--Useful for touch

- Tested with hyprland 0.55.4 and noctalia v5.0.0
- Should work with sway using iio-sway (not tested)

* add en.json to translation
unify settings keys for consistent naming
update names
add iio-service so tools work independently
refactor code for readability and remove unused logic
update readme
add thumbnail

* Updated plugin.toml names
Reworked panel code

* bump version

* Make arrow buttons apply relative orientation instead of fixed original
directions

* fixed wrong labels

* Delete iio_lock/iio_lock.luau

* Delete iio_lock/iio_panel.luau

* Delete iio_lock/shortcut.luau

* make noctalia.getConfig the sole config accessor

---------

Co-authored-by: Lemmy <studio@quadbyte.net>
This commit is contained in:
Nikolaj Holck Zwergius
2026-07-15 00:27:58 -04:00
committed by GitHub
co-authored by Lemmy
parent 2d637f9651
commit ae06d34fcb
9 changed files with 415 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
iio-lock — Orientation Lock for 2‑in‑1 Devices
iio-lock is a small bar widget and panel plugin for Noctalia that provides manual control over screen orientation.
It works by stopping and restarting the iio-hyprland service (or similar IIO rotation tools), preventing automatic rotation while the lock is active.
This is useful on 2‑in‑1 touch laptops, where automatic rotation may not always be desired — such as when using the device in tablet mode, drawing, or note‑taking.
Supported WM and Tools
Hyprland / iio‑hyprland — Working and tested
Sway / iio‑sway — Unverified (experimental)
External commands
This plugin shells out to:
-pgrep
-pkill
-iio-hyprland (or the user‑configured tool)
+32
View File
@@ -0,0 +1,32 @@
--!nonstrict
local locked = noctalia.state.get("iio-state")
local glyph_unlocked = noctalia.getConfig("unlocked")
local glyph_locked = noctalia.getConfig("locked")
local locked_text = noctalia.tr("widget.locked")
local unlocked_text = noctalia.tr("widget.unlocked")
local function render()
shortcut.setLabel(if locked then locked_text else unlocked_text)
if locked then
shortcut.setIcon(glyph_locked)
else
shortcut.setIcon(glyph_unlocked)
end
shortcut.setActive(locked)
end
noctalia.state.watch("iio-state", function (value)
locked = value
render()
end)
function onClick()
locked = not locked
noctalia.state.set("iio-state", locked)
render()
end
-- Initial state at load.
render()
+38
View File
@@ -0,0 +1,38 @@
--!nonstrict
-- Config
local glyph_unlocked = noctalia.getConfig("unlocked")
local glyph_locked = noctalia.getConfig("locked")
-- State
local locked = false
local glyph = glyph_unlocked
local function render()
barWidget.setGlyph(glyph)
end
local function apply_lock_state()
if locked then
glyph = glyph_locked
else
glyph = glyph_unlocked
end
end
function onClick()
locked = not locked
noctalia.state.set("iio-state",locked)
render()
end
noctalia.state.watch("iio-state", function (value)
locked = value
apply_lock_state()
render()
end)
function onRightClick()
noctalia.togglePanel("nikolaj-zwergius/iio_lock:panel")
end
render()
+30
View File
@@ -0,0 +1,30 @@
--!nonstrict
local glyph_unlocked = noctalia.getConfig("unlocked")
local glyph_locked = noctalia.getConfig("locked")
local locked = false
local glyph = glyph_unlocked
local function render()
barWidget.setGlyph(glyph)
end
local function apply_lock_state()
if locked then
glyph = glyph_locked
else
glyph = glyph_unlocked
end
end
noctalia.state.watch("iio-state", function (value)
locked = value
apply_lock_state()
render()
end)
function onClick()
noctalia.togglePanel("nikolaj-zwergius/iio_lock:panel")
end
render()
+39
View File
@@ -0,0 +1,39 @@
--!nonstrict
local iio_tool = noctalia.getConfig("iio")
local locked = false
-- Detect initial state by checking whether the IIO tool is running
local function refresh_state()
noctalia.runAsync("pgrep -fx "..iio_tool, function(result)
local pid = noctalia.string.trim(result.stdout)
if pid ~= "" then
noctalia.state.set("iio-state",false)
else
noctalia.state.set("iio-state",true)
end
end)
end
refresh_state()
local function kill_iio()
noctalia.runAsync("pkill -x "..iio_tool)
end
local function start_iio()
noctalia.runAsync(iio_tool)
end
local function apply_lock_state()
if locked then
kill_iio()
else
start_iio()
end
end
noctalia.state.watch("iio-state", function (value)
locked = value
apply_lock_state()
end)
+136
View File
@@ -0,0 +1,136 @@
--!nonstrict
--config
local used_vm = noctalia.getConfig("vm")
local transform_order_string = noctalia.getConfig("transform-order")
local transform_order = {}
local function set_transform_order(order_string)
local index = 0
for i in string.gmatch(order_string, "%d+") do
transform_order[index] = tonumber(i)
index += 1
end
end
set_transform_order(transform_order_string)
--vm functions
function hl(value,screen)
screen = screen or "eDP-1"
local cmd = string.format([[hyprctl eval 'hl.monitor({ output = "%s", transform = %d })']], screen, value)
noctalia.runAsync(cmd)
cmd = string.format([[hyprctl eval 'hl.config({input = {touchdevice = { transform = %d },tablet= { transform = %d }}})']],value,value)
noctalia.runAsync(cmd)
cmd = string.format([[hyprctl eval 'hl.workspace_rule({workspace = "m[%s]"})']],screen)
noctalia.runAsync(cmd)
end
function sw(value,screen)
screen = screen or "eDP-1"
local cmd = string.format("swaymsg \"output %s transform %d\"",screen,value)
noctalia.runAsync(cmd)
end
local wm = {
hyprland = hl,
sway = sw,
}
--render and layout
local function section(title, control)
return ui.column({ gap = 8 }, {
ui.label({ text = title, fontWeight = "medium", color = "on_surface_variant" }),
control,
})
end
local function render()
panel.render(ui.column({ flexGrow = 1, gap = 12 }, {
ui.row({ align = "center", justify = "space_between", gap = 12 }, {
ui.label({ text = noctalia.tr("panel.title"), fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }),
ui.button({ glyph = "close", onClick = "onCloseClicked" }),
}),
section("", ui.column({ gap = 12, align = "center" }, {
ui.button({width = 60 , height=100, onClick = "onUp",glyph="arrow-big-up-filled",glyphSize=50}),
section("", ui.row({ gap = 12, align = "center" }, {
ui.button({width = 100 , height=60, onClick = "onLeft",glyph="arrow-big-left-filled",glyphSize=50}),
ui.button({width = 100 , height=60, onClick = "onLock",glyph="lock",glyphSize=50}),
ui.button({width = 100 , height=60, onClick = "onRight",glyph="arrow-big-right-filled",glyphSize=50})
})),
ui.button({width = 60 , height=100, onClick = "onDown",glyph="arrow-big-down-filled",glyphSize=50}),
})),
}))
end
--actions
function onOpen(_context)
render()
end
function onLock()
local locked = noctalia.state.get("iio-state")
noctalia.state.set("iio-state",not locked)
panel.close()
end
function onUp()
wm[used_vm](transform_order[0])
noctalia.state.set("iio-state",true)
set_transform_order(table.concat({
transform_order[0],
transform_order[1],
transform_order[2],
transform_order[3],
}, ","))
panel.close()
end
function onLeft()
wm[used_vm](transform_order[1])
noctalia.state.set("iio-state",true)
set_transform_order(table.concat({
transform_order[1],
transform_order[2],
transform_order[3],
transform_order[0],
}, ","))
panel.close()
end
function onDown()
wm[used_vm](transform_order[2])
noctalia.state.set("iio-state",true)
set_transform_order(table.concat({
transform_order[2],
transform_order[3],
transform_order[0],
transform_order[1],
}, ","))
panel.close()
end
function onRight()
wm[used_vm](transform_order[3])
noctalia.state.set("iio-state",true)
set_transform_order(table.concat({
transform_order[3], --up
transform_order[0], --left
transform_order[1], --rig
transform_order[2], --down
}, ","))
panel.close()
end
function onCloseClicked()
panel.close()
end
noctalia.state.watch("iio-state", function (value)
if not value then
set_transform_order(transform_order_string)
end
end)
+95
View File
@@ -0,0 +1,95 @@
id = "nikolaj-zwergius/iio_lock"
name = "iio-lock"
version = "1.0.0"
min_noctalia = "5.0.0"
author = "nikolaj-zwergius"
license = "MIT"
dependencies = ["pgrep","pkill","iio-hyprland","iio-sway","hyprctl","swaymsg"]
tags = ["hyprland","sway","desktop","utility"]
icon = "lock"
description = "Orientation lock for 2‑in‑1 devices using iio-hyprland and similar."
[[setting]]
key = "iio"
type = "select"
label_key = "settings.iio.label"
description_key = "settings.iio.description"
default = "iio-hyprland"
options = [
{ value = "iio-hyprland", label_key = "settings.iio.options.hyprland" },
{ value = "iio-sway", label_key = "settings.iio.options.sway" },
]
[[setting]]
key = "transform-order"
type = "string"
label_key = "settings.transform.label"
description_key = "settings.transform.description"
default = "0,1,2,3"
[[setting]]
key = "vm"
type = "select"
label_key = "settings.wm.label"
description_key = "settings.wm.description"
default = "hyprland"
options = [
{ value = "hyprland", label_key = "settings.wm.options.hyprland" },
{ value = "sway", label_key = "settings.wm.options.sway" },
]
[[setting]]
key = "locked"
type = "glyph"
label_key = "settings.glyph.locked.label"
description_key = "settings.glyph.locked.description"
default = "lock"
[[setting]]
key = "unlocked"
type = "glyph"
label_key = "settings.glyph.unlocked.label"
description_key = "settings.glyph.unlocked.description"
default = "lock-open-2"
[[service]]
id = "iio-service"
entry = "iio-service.luau"
[[widget]]
id = "iio-lock"
entry = "iio-lock.luau"
[[widget]]
id = "lock-panel"
entry = "iio-panel.luau"
[[widget.setting]]
key = "unlocked"
type = "glyph"
label_key = "settings.glyph.unlocked.label"
description_key = "settings.glyph.unlocked.description"
default = "rotate-rectangle"
[[widget.setting]]
key = "locked"
type = "glyph"
label_key = "settings.glyph.locked.label"
description_key = "settings.glyph.locked.description"
default = "rotate-rectangle"
[[shortcut]]
id = "toggle"
entry = "cc-toggle.luau"
[[panel]]
id = "panel"
entry = "panel.luau"
width = 420
height = 410
placement = "floating"
position = "center"
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+29
View File
@@ -0,0 +1,29 @@
{
"settings.iio.label": "IIO Tool",
"settings.iio.description": "Executable used to control orientation",
"settings.iio.options.sway":"iio-sway",
"settings.iio.options.hyprland":"iio-hyprland",
"settings.transform.label": "Transform Order",
"settings.transform.description":"Rotation values for Up, Left, Down, Right.",
"settings.wm.label": "Window Manager",
"settings.wm.description": "Choose the window manager backend.",
"settings.wm.options.sway":"Sway",
"settings.wm.options.hyprland":"Hyprland",
"settings.glyph.locked.label": "Locked Glyph",
"settings.glyph.locked.description": "Icon displayed when the device is locked.",
"settings.glyph.unlocked.label": "Unlocked Glyph",
"settings.glyph.unlocked.description": "Icon displayed when the device is unlocked.",
"panel.title": "iio-lock",
"panel.lock": "Toggle Lock",
"widget.locked": "Locked",
"widget.unlocked": "Unlocked",
"toggle.description": "Toggle orientation lock"
}