diff --git a/iio_lock/README.md b/iio_lock/README.md new file mode 100644 index 0000000..4f64eea --- /dev/null +++ b/iio_lock/README.md @@ -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) diff --git a/iio_lock/cc-toggle.luau b/iio_lock/cc-toggle.luau new file mode 100644 index 0000000..974fa24 --- /dev/null +++ b/iio_lock/cc-toggle.luau @@ -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() diff --git a/iio_lock/iio-lock.luau b/iio_lock/iio-lock.luau new file mode 100644 index 0000000..adbfa23 --- /dev/null +++ b/iio_lock/iio-lock.luau @@ -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() diff --git a/iio_lock/iio-panel.luau b/iio_lock/iio-panel.luau new file mode 100644 index 0000000..fccf2d1 --- /dev/null +++ b/iio_lock/iio-panel.luau @@ -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() diff --git a/iio_lock/iio-service.luau b/iio_lock/iio-service.luau new file mode 100644 index 0000000..2393dc4 --- /dev/null +++ b/iio_lock/iio-service.luau @@ -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) diff --git a/iio_lock/panel.luau b/iio_lock/panel.luau new file mode 100644 index 0000000..4b9c1f2 --- /dev/null +++ b/iio_lock/panel.luau @@ -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) diff --git a/iio_lock/plugin.toml b/iio_lock/plugin.toml new file mode 100644 index 0000000..7a15c5c --- /dev/null +++ b/iio_lock/plugin.toml @@ -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" \ No newline at end of file diff --git a/iio_lock/thumbnail.webp b/iio_lock/thumbnail.webp new file mode 100644 index 0000000..21e1c4c Binary files /dev/null and b/iio_lock/thumbnail.webp differ diff --git a/iio_lock/translations/en.json b/iio_lock/translations/en.json new file mode 100644 index 0000000..6d4f88c --- /dev/null +++ b/iio_lock/translations/en.json @@ -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" +}