Add output to API

This commit is contained in:
Ottatop 2023-07-11 11:59:38 -05:00 committed by Ottatop
parent 930925a8f1
commit d91c06dbe9
13 changed files with 415 additions and 102 deletions

View file

@ -15,6 +15,7 @@ require("pinnacle").setup(function(pinnacle)
local window = pinnacle.window -- Window management local window = pinnacle.window -- Window management
local process = pinnacle.process -- Process spawning local process = pinnacle.process -- Process spawning
local tag = pinnacle.tag -- Tag management local tag = pinnacle.tag -- Tag management
local output = pinnacle.output -- Output management
-- Every key supported by xkbcommon. -- Every key supported by xkbcommon.
-- Support for just putting in a string of a key is intended. -- Support for just putting in a string of a key is intended.
@ -27,6 +28,7 @@ require("pinnacle").setup(function(pinnacle)
local terminal = "alacritty" local terminal = "alacritty"
-- Keybinds ---------------------------------------------------------------------- -- Keybinds ----------------------------------------------------------------------
input.keybind({ mod_key, "Alt" }, keys.q, pinnacle.quit) input.keybind({ mod_key, "Alt" }, keys.q, pinnacle.quit)
input.keybind({ mod_key, "Alt" }, keys.c, window.close_window) input.keybind({ mod_key, "Alt" }, keys.c, window.close_window)
@ -49,8 +51,18 @@ require("pinnacle").setup(function(pinnacle)
process.spawn("nautilus") process.spawn("nautilus")
end) end)
input.keybind({ mod_key }, keys.g, function()
local op = output.get_by_res(2560, 1440)
for _, v in pairs(op) do
print(v.name)
end
end)
-- Tags --------------------------------------------------------------------------- -- Tags ---------------------------------------------------------------------------
tag.add("1", "2", "3", "4", "5")
output.connect_for_all(function(op)
tag.add(op, "1", "2", "3", "4", "5")
end)
tag.toggle("1") tag.toggle("1")
input.keybind({ mod_key }, keys.KEY_1, function() input.keybind({ mod_key }, keys.KEY_1, function()

View file

@ -9,9 +9,19 @@ local input = {
} }
---Set a keybind. If called with an already existing keybind, it gets replaced. ---Set a keybind. If called with an already existing keybind, it gets replaced.
---
---# Example
---
---```lua
----- The following sets Super + Return to open Alacritty
---
---input.keybind({ "Super" }, input.keys.Return, function()
--- process.spawn("Alacritty")
---end)
---```
---@param key Keys The key for the keybind. ---@param key Keys The key for the keybind.
---@param modifiers (Modifier)[] Which modifiers need to be pressed for the keybind to trigger. ---@param modifiers (Modifier)[] Which modifiers need to be pressed for the keybind to trigger.
---@param action fun() What to run. ---@param action fun() What to do.
function input.keybind(modifiers, key, action) function input.keybind(modifiers, key, action)
table.insert(CallbackTable, action) table.insert(CallbackTable, action)
SendMsg({ SendMsg({

View file

@ -7,7 +7,7 @@
---@alias Modifier "Alt" | "Ctrl" | "Shift" | "Super" ---@alias Modifier "Alt" | "Ctrl" | "Shift" | "Super"
---@enum Keys ---@enum Keys
local M = { local keys = {
NoSymbol = 0x00000000, NoSymbol = 0x00000000,
VoidSymbol = 0x00ffffff, VoidSymbol = 0x00ffffff,
@ -4321,4 +4321,4 @@ local M = {
block = 0x100000fc, block = 0x100000fc,
} }
return M return keys

View file

@ -7,7 +7,7 @@
---@meta _ ---@meta _
---@class _Msg ---@class _Msg
---@field SetKeybind { key: Keys, modifiers: Modifiers[], callback_id: integer } ---@field SetKeybind { key: Keys, modifiers: Modifier[], callback_id: integer }
---@field SetMousebind { button: integer } ---@field SetMousebind { button: integer }
--Windows --Windows
---@field CloseWindow { client_id: integer? } ---@field CloseWindow { client_id: integer? }
@ -21,25 +21,38 @@
--Tags --Tags
---@field ToggleTag { tag_id: string } ---@field ToggleTag { tag_id: string }
---@field SwitchToTag { tag_id: string } ---@field SwitchToTag { tag_id: string }
---@field AddTags { tags: string[] } ---@field AddTags { output_name: string, tags: string[] }
---@field RemoveTags { tags: string[] } ---@field RemoveTags { output_name: string, tags: string[] }
--Outputs
---@field ConnectForAllOutputs { callback_id: integer }
---@alias Msg _Msg | "Quit" ---@alias Msg _Msg | "Quit"
---@class Request --------------------------------------------------------------------------------------------
---@field GetWindowByFocus { id: integer }
---@field GetAllWindows { id: integer } ---@class _Request
--Windows
---@field GetWindowByAppId { app_id: string }
---@field GetWindowByTitle { title: string }
--Outputs
---@field GetOutputByName { name: string }
---@field GetOutputsByModel { model: string }
---@field GetOutputsByRes { res: integer[] }
---@alias Request _Request | "GetWindowByFocus" | "GetAllWindows"
---@class IncomingMsg ---@class IncomingMsg
---@field CallCallback { callback_id: integer, args: Args } ---@field CallCallback { callback_id: integer, args: Args }
---@field RequestResponse { request_id: integer, response: RequestResponse } ---@field RequestResponse { response: RequestResponse }
---@class Args ---@class Args
---@field Spawn { stdout: string?, stderr: string?, exit_code: integer?, exit_msg: string? } ---@field Spawn { stdout: string?, stderr: string?, exit_code: integer?, exit_msg: string? }
---@field ConnectForAllOutputs { output_name: string }
---@class RequestResponse ---@class RequestResponse
---@field Window { window: WindowProperties } ---@field Window { window: WindowProperties }
---@field GetAllWindows { windows: WindowProperties[] } ---@field GetAllWindows { windows: WindowProperties[] }
---@field Outputs { names: string[] }
---@class WindowProperties ---@class WindowProperties
---@field id integer ---@field id integer

133
api/lua/output.lua Normal file
View file

@ -0,0 +1,133 @@
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
--
-- SPDX-License-Identifier: MPL-2.0
---@class Output A display.
---@field name string The name of this output (or rather, of its connector).
local op = {}
---Add methods to this output.
---@param props Output
---@return Output
local function new_output(props)
-- Copy functions over
for k, v in pairs(op) do
props[k] = v
end
return props
end
------------------------------------------------------
local output = {}
---Get an output by its name.
---
---"Name" in this sense does not mean its model or manufacturer;
---rather, "name" is the name of the connector the output is connected to.
---This should be something like "HDMI-A-0", "eDP-1", or similar.
---
---# Examples
---```lua
---local monitor = output.get_by_name("DP-1")
---print(monitor.name) -- should print `DP-1`
---```
---@param name string The name of the output.
---@return Output|nil
function output.get_by_name(name)
SendMsg({
Request = {
GetOutputByName = {
name = name,
},
},
})
local response = ReadMsg()
local names = response.RequestResponse.response.Outputs.names
if names[1] ~= nil then
return new_output({ name = names[1] })
else
return nil
end
end
---NOTE: This may or may not be what is reported by other monitor listing utilities. One of my monitors fails to report itself in Smithay when it is correctly picked up by tools like wlr-randr. I'll fix this in the future.
---
---Get outputs by their model.
---This is something like "DELL E2416H" or whatever gibberish monitor manufacturers call their displays.
---@param model string The model of the output(s).
---@return Output[] outputs All outputs with this model. If there are none, the returned table will be empty.
function output.get_by_model(model)
SendMsg({
Request = {
GetOutputsByModel = {
model = model,
},
},
})
local response = ReadMsg()
local names = response.RequestResponse.response.Outputs.names
---@type Output
local outputs = {}
for _, v in pairs(names) do
table.insert(outputs, new_output({ name = v }))
end
return outputs
end
---Get outputs by their resolution.
---
---@param width integer The width of the outputs, in pixels.
---@param height integer The height of the outputs, in pixels.
---@return Output[] outputs All outputs with this resolution. If there are none, the returned table will be empty.
function output.get_by_res(width, height)
SendMsg({
Request = {
GetOutputsByRes = {
res = { width, height },
},
},
})
local response = ReadMsg()
local names = response.RequestResponse.response.Outputs.names
---@type Output
local outputs = {}
for _, v in pairs(names) do
table.insert(outputs, new_output({ name = v }))
end
return outputs
end
---Connect a function to be run on all current and future outputs.
---
---When called, `connect_for_all` will immediately run `func` with all currently connected outputs.
---If a new output is connected, `func` will also be called with it.
---@param func fun(output: Output) The function that will be run.
function output.connect_for_all(func)
---@param args Args
table.insert(CallbackTable, function(args)
local args = args.ConnectForAllOutputs
func(new_output({ name = args.output_name }))
end)
SendMsg({
ConnectForAllOutputs = {
callback_id = #CallbackTable,
},
})
end
return output

View file

@ -56,6 +56,8 @@ local pinnacle = {
process = require("process"), process = require("process"),
---Tag management ---Tag management
tag = require("tag"), tag = require("tag"),
---Output management
output = require("output"),
} }
---Quit Pinnacle. ---Quit Pinnacle.
@ -114,15 +116,6 @@ function pinnacle.setup(config_func)
return tb return tb
end end
Requests = {
id = 1,
}
function Requests:next()
local id = self.id
self.id = self.id + 1
return id
end
config_func(pinnacle) config_func(pinnacle)
while true do while true do

View file

@ -8,36 +8,62 @@ local tag = {}
---Add tags. ---Add tags.
--- ---
---If you need to add the strings in a table, use `tag.add_table` instead. ---If you need to add the names as a table, use `tag.add_table` instead.
--- ---
---# Example ---# Example
--- ---
---```lua ---```lua
---tag.add("1", "2", "3", "4", "5") -- Add tags with names 1-5 ---local output = output.get_by_name("DP-1")
---if output ~= nil then
--- tag.add(output, "1", "2", "3", "4", "5") -- Add tags with names 1-5
---end
---``` ---```
---@param output Output The output you want these tags to be added to.
---@param ... string The names of the new tags you want to add. ---@param ... string The names of the new tags you want to add.
function tag.add(...) function tag.add(output, ...)
local tags = table.pack(...) local tags = table.pack(...)
tags["n"] = nil tags["n"] = nil
SendMsg({ SendMsg({
AddTags = { AddTags = {
output_name = output.name,
tags = tags, tags = tags,
}, },
}) })
end end
---Like `tag.add`, but with a table of strings instead. ---Like `tag.add`, but with a table of strings instead.
---
---# Example
---
---```lua
---local tags = { "Terminal", "Browser", "Mail", "Gaming", "Potato" }
---local output = output.get_by_name("DP-1")
---if output ~= nil then
--- tag.add(output, tags) -- Add tags with the names above
---end
---```
---@param output Output The output you want these tags to be added to.
---@param tags string[] The names of the new tags you want to add, as a table. ---@param tags string[] The names of the new tags you want to add, as a table.
function tag.add_table(tags) function tag.add_table(output, tags)
SendMsg({ SendMsg({
AddTags = { AddTags = {
output_name = output.name,
tags = tags, tags = tags,
}, },
}) })
end end
---Toggle a tag's display. ---Toggle a tag on the currently focused output.
---
---# Example
---
---```lua
----- Assuming all tags are toggled off...
---tag.toggle("1")
---tag.toggle("2")
----- will cause windows on both tags 1 and 2 to be displayed at the same time.
---```
---@param name string The name of the tag. ---@param name string The name of the tag.
function tag.toggle(name) function tag.toggle(name)
SendMsg({ SendMsg({
@ -47,7 +73,15 @@ function tag.toggle(name)
}) })
end end
---Switch to a tag, deactivating any other active tags. ---Switch to a tag on the currently focused output, deactivating any other active tags on that output.
---
---This is used to replicate what a traditional workspace is on some other Wayland compositors.
---
---# Example
---
---```lua
---tag.switch_to("3") -- Switches to and displays *only* windows on tag 3
---```
---@param name string The name of the tag. ---@param name string The name of the tag.
function tag.switch_to(name) function tag.switch_to(name)
SendMsg({ SendMsg({

View file

@ -13,7 +13,7 @@
---@field private floating boolean Whether the window is floating or not (tiled) ---@field private floating boolean Whether the window is floating or not (tiled)
local win = {} local win = {}
---@param props { id: integer, app_id: string?, title: string?, size: { w: integer, h: integer }, location: { x: integer, y: integer }, floating: boolean } ---@param props Window
---@return Window ---@return Window
local function new_window(props) local function new_window(props)
-- Copy functions over -- Copy functions over
@ -91,15 +91,14 @@ function window.toggle_floating(client_id)
}) })
end end
---TODO: This function is not implemented yet.
---
---Get a window by its app id (aka its X11 class). ---Get a window by its app id (aka its X11 class).
---@param app_id string The window's app id. For example, Alacritty's app id is "Alacritty". ---@param app_id string The window's app id. For example, Alacritty's app id is "Alacritty".
---@return Window window -- TODO: nil ---@return Window window -- TODO: nil
function window.get_by_app_id(app_id) function window.get_by_app_id(app_id)
local req_id = Requests:next()
SendRequest({ SendRequest({
GetWindowByAppId = { GetWindowByAppId = {
id = req_id,
app_id = app_id, app_id = app_id,
}, },
}) })
@ -127,15 +126,14 @@ function window.get_by_app_id(app_id)
return new_window(wind) return new_window(wind)
end end
---TODO: This function is not implemented yet.
---
---Get a window by its title. ---Get a window by its title.
---@param title string The window's title. ---@param title string The window's title.
---@return Window ---@return Window
function window.get_by_title(title) function window.get_by_title(title)
local req_id = Requests:next()
SendRequest({ SendRequest({
GetWindowByTitle = { GetWindowByTitle = {
id = req_id,
title = title, title = title,
}, },
}) })
@ -166,13 +164,7 @@ end
---Get the currently focused window. ---Get the currently focused window.
---@return Window ---@return Window
function window.get_focused() function window.get_focused()
local req_id = Requests:next() SendRequest("GetWindowByFocus")
SendRequest({
GetWindowByFocus = {
id = req_id,
},
})
local response = ReadMsg() local response = ReadMsg()
@ -199,12 +191,8 @@ end
---Get all windows. ---Get all windows.
---@return Window[] ---@return Window[]
function window.get_windows() function window.get_all()
SendRequest({ SendRequest("GetAllWindows")
GetAllWindows = {
id = Requests:next(),
},
})
-- INFO: these read synchronously so this should always work IF the server works correctly -- INFO: these read synchronously so this should always work IF the server works correctly

View file

@ -17,7 +17,7 @@ pub enum Msg {
// Input // Input
SetKeybind { SetKeybind {
key: u32, key: u32,
modifiers: Vec<Modifiers>, modifiers: Vec<Modifier>,
callback_id: CallbackId, callback_id: CallbackId,
}, },
SetMousebind { SetMousebind {
@ -47,20 +47,30 @@ pub enum Msg {
}, },
// Tag management // Tag management
// FIXME: tag_id should not be a string
ToggleTag { ToggleTag {
tag_id: String, tag_id: String,
}, },
// FIXME: tag_id should not be a string
SwitchToTag { SwitchToTag {
tag_id: String, tag_id: String,
}, },
AddTags { AddTags {
/// The name of the output you want these tags on.
output_name: String,
tags: Vec<String>, tags: Vec<String>,
}, },
RemoveTags { RemoveTags {
// TODO: /// The name of the output you want these tags removed from.
output_name: String,
tags: Vec<String>, tags: Vec<String>,
}, },
// Output management
ConnectForAllOutputs {
callback_id: CallbackId,
},
// Process management // Process management
/// Spawn a program with an optional callback. /// Spawn a program with an optional callback.
Spawn { Spawn {
@ -82,14 +92,17 @@ pub struct RequestId(pub u32);
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
/// Messages that require a server response, usually to provide some data. /// Messages that require a server response, usually to provide some data.
pub enum Request { pub enum Request {
GetWindowByAppId { id: RequestId, app_id: String }, GetWindowByAppId { app_id: String },
GetWindowByTitle { id: RequestId, title: String }, GetWindowByTitle { title: String },
GetWindowByFocus { id: RequestId }, GetWindowByFocus,
GetAllWindows { id: RequestId }, GetAllWindows,
GetOutputByName { name: String },
GetOutputsByModel { model: String },
GetOutputsByRes { res: (u32, u32) },
} }
#[derive(Debug, PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum Modifiers { pub enum Modifier {
Shift = 0b0000_0001, Shift = 0b0000_0001,
Ctrl = 0b0000_0010, Ctrl = 0b0000_0010,
Alt = 0b0000_0100, Alt = 0b0000_0100,
@ -100,7 +113,7 @@ pub enum Modifiers {
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub struct ModifierMask(u8); pub struct ModifierMask(u8);
impl<T: IntoIterator<Item = Modifiers>> From<T> for ModifierMask { impl<T: IntoIterator<Item = Modifier>> From<T> for ModifierMask {
fn from(value: T) -> Self { fn from(value: T) -> Self {
let value = value.into_iter(); let value = value.into_iter();
let mut mask: u8 = 0b0000_0000; let mut mask: u8 = 0b0000_0000;
@ -112,19 +125,19 @@ impl<T: IntoIterator<Item = Modifiers>> From<T> for ModifierMask {
} }
impl ModifierMask { impl ModifierMask {
pub fn values(self) -> Vec<Modifiers> { pub fn values(self) -> Vec<Modifier> {
let mut res = Vec::<Modifiers>::new(); let mut res = Vec::<Modifier>::new();
if self.0 & Modifiers::Shift as u8 == Modifiers::Shift as u8 { if self.0 & Modifier::Shift as u8 == Modifier::Shift as u8 {
res.push(Modifiers::Shift); res.push(Modifier::Shift);
} }
if self.0 & Modifiers::Ctrl as u8 == Modifiers::Ctrl as u8 { if self.0 & Modifier::Ctrl as u8 == Modifier::Ctrl as u8 {
res.push(Modifiers::Ctrl); res.push(Modifier::Ctrl);
} }
if self.0 & Modifiers::Alt as u8 == Modifiers::Alt as u8 { if self.0 & Modifier::Alt as u8 == Modifier::Alt as u8 {
res.push(Modifiers::Alt); res.push(Modifier::Alt);
} }
if self.0 & Modifiers::Super as u8 == Modifiers::Super as u8 { if self.0 & Modifier::Super as u8 == Modifier::Super as u8 {
res.push(Modifiers::Super); res.push(Modifier::Super);
} }
res res
} }
@ -139,7 +152,6 @@ pub enum OutgoingMsg {
args: Option<Args>, args: Option<Args>,
}, },
RequestResponse { RequestResponse {
request_id: RequestId,
response: RequestResponse, response: RequestResponse,
}, },
} }
@ -157,10 +169,14 @@ pub enum Args {
#[serde(default)] #[serde(default)]
exit_msg: Option<String>, exit_msg: Option<String>,
}, },
ConnectForAllOutputs {
output_name: String,
},
} }
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum RequestResponse { pub enum RequestResponse {
Window { window: WindowProperties }, Window { window: WindowProperties },
GetAllWindows { windows: Vec<WindowProperties> }, GetAllWindows { windows: Vec<WindowProperties> },
Outputs { names: Vec<String> },
} }

View file

@ -97,6 +97,7 @@ use smithay_drm_extras::{
}; };
use crate::{ use crate::{
api::msg::{Args, OutgoingMsg},
render::{pointer::PointerElement, CustomRenderElements, OutputRenderElements}, render::{pointer::PointerElement, CustomRenderElements, OutputRenderElements},
state::{take_presentation_feedback, CalloopData, State, SurfaceDmabufFeedback}, state::{take_presentation_feedback, CalloopData, State, SurfaceDmabufFeedback},
}; };
@ -226,11 +227,6 @@ pub fn run_udev() -> Result<(), Box<dyn Error>> {
pointer_element: PointerElement::default(), pointer_element: PointerElement::default(),
}; };
//
//
//
//
let mut state = State::<UdevData>::init( let mut state = State::<UdevData>::init(
data, data,
&mut display, &mut display,
@ -852,6 +848,32 @@ impl State<UdevData> {
device_id: node, device_id: node,
}); });
// Run any connected callbacks
{
let clone = output.clone();
self.loop_handle.insert_idle(move |data| {
let stream = data
.state
.api_state
.stream
.as_ref()
.expect("Stream doesn't exist");
let mut stream = stream.lock().expect("Couldn't lock stream");
for callback_id in data.state.output_callback_ids.iter() {
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::CallCallback {
callback_id: *callback_id,
args: Some(Args::ConnectForAllOutputs {
output_name: clone.name(),
}),
},
)
.expect("Send to client failed");
}
});
}
let allocator = GbmAllocator::new( let allocator = GbmAllocator::new(
device.gbm.clone(), device.gbm.clone(),
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT, GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,

View file

@ -6,7 +6,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::api::msg::{CallbackId, ModifierMask, Modifiers, OutgoingMsg}; use crate::api::msg::{CallbackId, Modifier, ModifierMask, OutgoingMsg};
use smithay::{ use smithay::{
backend::input::{ backend::input::{
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent, AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent,
@ -221,18 +221,18 @@ impl<B: Backend> State<B> {
time, time,
|state, modifiers, keysym| { |state, modifiers, keysym| {
if press_state == KeyState::Pressed { if press_state == KeyState::Pressed {
let mut modifier_mask = Vec::<Modifiers>::new(); let mut modifier_mask = Vec::<Modifier>::new();
if modifiers.alt { if modifiers.alt {
modifier_mask.push(Modifiers::Alt); modifier_mask.push(Modifier::Alt);
} }
if modifiers.shift { if modifiers.shift {
modifier_mask.push(Modifiers::Shift); modifier_mask.push(Modifier::Shift);
} }
if modifiers.ctrl { if modifiers.ctrl {
modifier_mask.push(Modifiers::Ctrl); modifier_mask.push(Modifier::Ctrl);
} }
if modifiers.logo { if modifiers.logo {
modifier_mask.push(Modifiers::Super); modifier_mask.push(Modifier::Super);
} }
let raw_sym = if keysym.raw_syms().len() == 1 { let raw_sym = if keysym.raw_syms().len() == 1 {
keysym.raw_syms()[0] keysym.raw_syms()[0]

View file

@ -98,6 +98,10 @@ pub struct State<B: Backend> {
pub windows: Vec<Window>, pub windows: Vec<Window>,
pub async_scheduler: Scheduler<()>, pub async_scheduler: Scheduler<()>,
// TODO: move into own struct
// | basically just clean this mess up
pub output_callback_ids: Vec<CallbackId>,
} }
impl<B: Backend> State<B> { impl<B: Backend> State<B> {
@ -240,38 +244,59 @@ impl<B: Backend> State<B> {
self.re_layout(); self.re_layout();
} }
// TODO: add output // TODO: add output
Msg::AddTags { tags } => { Msg::AddTags { output_name, tags } => {
if let Some(output) = self if let Some(output) = self
.focus_state .space
.focused_output .outputs()
.as_ref() .find(|output| output.name() == output_name)
.or_else(|| self.space.outputs().next())
{ {
output.with_state(|state| { output.with_state(|state| {
state.tags.extend( state.tags.extend(tags.iter().cloned().map(Tag::new));
tags.clone()
.into_iter()
.map(|name| Tag::new(name, output.clone())),
);
}); });
} }
} }
Msg::RemoveTags { tags } => { Msg::RemoveTags { output_name, tags } => {
for output in self.space.outputs() { if let Some(output) = self
.space
.outputs()
.find(|output| output.name() == output_name)
{
output.with_state(|state| { output.with_state(|state| {
state.tags.retain(|tag| !tags.contains(&tag.name)); state.tags.retain(|tag| !tags.contains(&tag.name));
}); });
} }
} }
Msg::ConnectForAllOutputs { callback_id } => {
let stream = self
.api_state
.stream
.as_ref()
.expect("Stream doesn't exist");
let mut stream = stream.lock().expect("Couldn't lock stream");
for output in self.space.outputs() {
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::CallCallback {
callback_id,
args: Some(Args::ConnectForAllOutputs {
output_name: output.name(),
}),
},
)
.expect("Send to client failed");
}
self.output_callback_ids.push(callback_id);
}
Msg::Quit => { Msg::Quit => {
self.loop_signal.stop(); self.loop_signal.stop();
} }
Msg::Request(request) => match request { Msg::Request(request) => match request {
Request::GetWindowByAppId { id, app_id } => todo!(), Request::GetWindowByAppId { app_id } => todo!(),
Request::GetWindowByTitle { id, title } => todo!(), Request::GetWindowByTitle { title } => todo!(),
Request::GetWindowByFocus { id } => { Request::GetWindowByFocus => {
let Some(current_focus) = self.focus_state.current_focus() else { return; }; let Some(current_focus) = self.focus_state.current_focus() else { return; };
let (app_id, title) = let (app_id, title) =
compositor::with_states(current_focus.toplevel().wl_surface(), |states| { compositor::with_states(current_focus.toplevel().wl_surface(), |states| {
@ -304,13 +329,12 @@ impl<B: Backend> State<B> {
crate::api::send_to_client( crate::api::send_to_client(
&mut stream, &mut stream,
&OutgoingMsg::RequestResponse { &OutgoingMsg::RequestResponse {
request_id: id,
response: RequestResponse::Window { window: props }, response: RequestResponse::Window { window: props },
}, },
) )
.expect("Send to client failed"); .expect("Send to client failed");
} }
Request::GetAllWindows { id } => { Request::GetAllWindows => {
let window_props = self let window_props = self
.space .space
.elements() .elements()
@ -353,7 +377,6 @@ impl<B: Backend> State<B> {
crate::api::send_to_client( crate::api::send_to_client(
&mut stream, &mut stream,
&OutgoingMsg::RequestResponse { &OutgoingMsg::RequestResponse {
request_id: id,
response: RequestResponse::GetAllWindows { response: RequestResponse::GetAllWindows {
windows: window_props, windows: window_props,
}, },
@ -361,6 +384,78 @@ impl<B: Backend> State<B> {
) )
.expect("Couldn't send to client"); .expect("Couldn't send to client");
} }
Request::GetOutputByName { name } => {
let names = self
.space
.outputs()
.filter(|output| output.name() == name)
.map(|output| output.name())
.collect::<Vec<_>>();
let stream = self
.api_state
.stream
.as_ref()
.expect("Stream doesn't exist");
let mut stream = stream.lock().expect("Couldn't lock stream");
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::RequestResponse {
response: RequestResponse::Outputs { names },
},
)
.unwrap();
}
Request::GetOutputsByModel { model } => {
let names = self
.space
.outputs()
.filter(|output| output.physical_properties().model == model)
.map(|output| output.name())
.collect::<Vec<_>>();
let stream = self
.api_state
.stream
.as_ref()
.expect("Stream doesn't exist");
let mut stream = stream.lock().expect("Couldn't lock stream");
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::RequestResponse {
response: RequestResponse::Outputs { names },
},
)
.unwrap();
}
Request::GetOutputsByRes { res } => {
let names = self
.space
.outputs()
.filter_map(|output| {
if let Some(mode) = output.current_mode() {
if mode.size == (res.0 as i32, res.1 as i32).into() {
Some(output.name())
} else {
None
}
} else {
None
}
})
.collect::<Vec<_>>();
let stream = self
.api_state
.stream
.as_ref()
.expect("Stream doesn't exist");
let mut stream = stream.lock().expect("Couldn't lock stream");
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::RequestResponse {
response: RequestResponse::Outputs { names },
},
)
.unwrap();
}
}, },
} }
} }
@ -718,6 +813,7 @@ impl<B: Backend> State<B> {
async_scheduler: sched, async_scheduler: sched,
windows: vec![], windows: vec![],
output_callback_ids: vec![],
}) })
} }
} }
@ -779,6 +875,7 @@ pub fn take_presentation_feedback(
/// State containing the config API's stream. /// State containing the config API's stream.
#[derive(Default)] #[derive(Default)]
pub struct ApiState { pub struct ApiState {
// TODO: this may not need to be in an arc mutex because of the move to async
pub stream: Option<Arc<Mutex<UnixStream>>>, pub stream: Option<Arc<Mutex<UnixStream>>>,
} }

View file

@ -9,8 +9,6 @@ use std::{
sync::atomic::{AtomicU32, Ordering}, sync::atomic::{AtomicU32, Ordering},
}; };
use smithay::output::Output;
static TAG_ID_COUNTER: AtomicU32 = AtomicU32::new(0); static TAG_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
#[derive(Debug, Hash, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Hash, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
@ -28,19 +26,16 @@ pub struct Tag {
pub id: TagId, pub id: TagId,
/// The name of this tag. /// The name of this tag.
pub name: String, pub name: String,
/// The output that this tag should be on.
pub output: Output,
/// Whether this tag is active or not. /// Whether this tag is active or not.
pub active: bool, pub active: bool,
// TODO: layout // TODO: layout
} }
impl Tag { impl Tag {
pub fn new(name: String, output: Output) -> Self { pub fn new(name: String) -> Self {
Self { Self {
id: TagId::next(), id: TagId::next(),
name, name,
output,
active: false, active: false,
} }
} }