Merge commit '28156e0c9d098738f56a976ac8fa983bf22cf6b8' as 'kiwmi'

phoen
Daniel Barlow 2022-04-26 15:34:04 +01:00
commit bbb9d40c69
60 changed files with 8717 additions and 0 deletions

18
kiwmi/.cirrus.yml Normal file
View File

@ -0,0 +1,18 @@
task:
container:
image: archlinux:base-devel
matrix:
- name: Build (clang)
script:
- sudo pacman --noconfirm -Syu
- sudo pacman --noconfirm -S git meson ninja lua51 wlroots wayland-protocols
- meson build -Dlua-pkg=lua51 --werror
- ninja -C build
- name: Format
script:
- sudo pacman --noconfirm -Syu
- sudo pacman --noconfirm -S clang git
- find -name .git -prune -o -type f -name '*.[ch]' -print | xargs -d '\n' clang-format -i
- git diff --exit-code

49
kiwmi/.clang-format Normal file
View File

@ -0,0 +1,49 @@
---
Language: Cpp
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: AllDefinitions
AlwaysBreakBeforeMultilineStrings: true
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Linux
BreakBeforeTernaryOperators: true
BreakStringLiterals: true
ColumnLimit: 80
ContinuationIndentWidth: 4
IndentCaseLabels: false
IndentPPDirectives: AfterHash
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 1
PointerAlignment: Right
ReflowComments: false
SortIncludes: true
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
UseTab: Never
ForEachMacros:
- wl_list_for_each
- wl_list_for_each_safe
- wl_list_for_each_reverse
- wl_list_for_each_reverse_safe
- wl_array_for_each
...

17
kiwmi/.editorconfig Normal file
View File

@ -0,0 +1,17 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
tab_width = 4
[meson.build]
indent_size = 2
tab_width = 2
[*.yml]
indent_size = 2
tab_width = 2

1
kiwmi/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

30
kiwmi/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,30 @@
# Contributing Guidelines
Contributing entails that all contributions follow the [MPL 2.0 license](https://github.com/buffet/kiwmi/blob/master/LICENSE).
## Opening Issues
- Ensure that bugs have proper steps to reproduce
- Include program version, along with any system specs that may help identify the issue
## Committing
Commit messages should be both _clear_ and _descriptive_. If possible, commit titles should start with a verb describing the change. Don't be shy to include additional information such as motivation for the change in the commit body. Be sure to ensure other developers will be able to understand _why_ a specific change has occurred in the future.
## Code formatting
To ensure consistent formatting, use `clang-format -i **/*.[ch]` (beware, not all shells support that glob). Don't worry too much about forgetting to do so, the pipeline will remind you by failing on your commit/PR. You can also make this your pre-commit git hook (see `man 5 githooks`) to avoid committing any misformatted code:
```sh
#!/bin/sh
# Check if there're any formatting errors
find . '(' -name .git -o -path ./build ')' -prune \
-o -type f -name '*.[ch]' -print |
xargs -d '\n' clang-format --dry-run >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo 'Code formatting is wrong, fix it before commit' >/dev/stderr
exit 1
fi
```

374
kiwmi/LICENSE Normal file
View File

@ -0,0 +1,374 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
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 http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

69
kiwmi/README.md Normal file
View File

@ -0,0 +1,69 @@
<h1 align="center">kiwmi</h1>
<p align="center"><i>A fully programmable Wayland Compositor</i></p>
<hr><p align="center">
<img alt="Stars" src="https://img.shields.io/github/stars/buffet/kiwmi.svg?label=Stars&style=flat" />
<a href="https://cirrus-ci.com/github/buffet/kiwmi"><img alt="Build Status" src="https://api.cirrus-ci.com/github/buffet/kiwmi.svg"></a>
<a href="https://github.com/buffet/kiwmi/issues"><img alt="GitHub Issues" src="https://img.shields.io/github/issues/buffet/kiwmi.svg"/></a>
<a href="https://github.com/buffet/kiwmi/graphs/contributors"><img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/buffet/kiwmi"></a>
</p>
kiwmi is a work-in-progress extensive user-configurable Wayland Compositor.
kiwmi specifically does not enforce any logic, allowing for the creation of Lua-scripted behaviors, making arduous tasks such as modal window management become a breeze.
New users should be aware of the steep learning curve present, however this will be reduced as the project matures.
Got any questions or want to discuss something? Join us in [#kiwmi on irc.libera.chat](https://web.libera.chat/gamja/?channels=#kiwmi)!
## Documentation
Documentation for the API can be found in [lua_docs.md](lua_docs.md).
Additionally `kiwmic` can be used to send a single lua string to kiwmi for direct evaluation.
For example:
```
$ kiwmic 'return kiwmi:focused_view():id()'
94036737803088.0
```
## Getting Started
The dependencies required are:
- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
- lua or luajit
- pixman
- meson (build)
- ninja (build)
- git (build, optional)
### Building
After cloning/downloading the project and ensuring all dependencies are installed, building is as easy as running
```
$ meson build
$ ninja -C build
```
If you plan to use luajit instead, use the following commands instead.
```
$ meson -Dlua-pkg=luajit build
$ ninja -C build
```
Installing is accomplished with the following command:
```
# ninja -C build install
```
## Contributing
Contributions are welcomed, especially while the project is in a heavy WIP stage.
If you believe you have a valid concern, read the [CONTRIBUTING](https://github.com/buffet/kiwmi/blob/master/CONTRIBUTING.md) document and please file an issue on the [issues page](https://github.com/buffet/kiwmi/issues/new).
For clarifications or suggestions on anything, please don't hesitate to contact me.

15
kiwmi/include/color.h Normal file
View File

@ -0,0 +1,15 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_COLOR_H
#define KIWMI_COLOR_H
#include <stdbool.h>
bool color_parse(const char *hex, float color[static 4]);
#endif /* KIWMI_COLOR_H */

View File

@ -0,0 +1,43 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_DESKTOP_DESKTOP_H
#define KIWMI_DESKTOP_DESKTOP_H
#include <wayland-server.h>
struct kiwmi_desktop {
struct wlr_compositor *compositor;
struct wlr_xdg_shell *xdg_shell;
struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager;
struct wlr_layer_shell_v1 *layer_shell;
struct wlr_data_device_manager *data_device_manager;
struct wlr_output_layout *output_layout;
struct wl_list outputs; // struct kiwmi_output::link
struct wl_list views; // struct kiwmi_view::link
float bg_color[4];
struct wl_listener xdg_shell_new_surface;
struct wl_listener xdg_toplevel_new_decoration;
struct wl_listener layer_shell_new_surface;
struct wl_listener new_output;
struct {
struct wl_signal new_output;
struct wl_signal view_map;
struct wl_signal request_active_output;
} events;
};
bool desktop_init(struct kiwmi_desktop *desktop);
void desktop_fini(struct kiwmi_desktop *desktop);
struct kiwmi_server;
struct kiwmi_output *desktop_active_output(struct kiwmi_server *server);
#endif /* KIWMI_DESKTOP_DESKTOP_H */

View File

@ -0,0 +1,45 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_DESKTOP_LAYER_SHELL_H
#define KIWMI_DESKTOP_LAYER_SHELL_H
#include <stdbool.h>
#include <wayland-server.h>
#include <wlr/types/wlr_surface.h>
#include <wlr/util/box.h>
#include "desktop/output.h"
struct kiwmi_layer {
struct wl_list link;
struct wlr_layer_surface_v1 *layer_surface;
uint32_t layer;
struct kiwmi_output *output;
struct wl_listener destroy;
struct wl_listener commit;
struct wl_listener map;
struct wl_listener unmap;
struct wlr_box geom;
};
void arrange_layers(struct kiwmi_output *output);
struct kiwmi_layer *layer_at(
struct wl_list *layers,
struct wlr_surface **surface,
double ox,
double oy,
double *sx,
double *sy);
void layer_shell_new_surface_notify(struct wl_listener *listener, void *data);
#endif /* KIWMI_DESKTOP_LAYER_SHELL_H */

View File

@ -0,0 +1,48 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_DESKTOP_OUTPUT_H
#define KIWMI_DESKTOP_OUTPUT_H
#include <wayland-server.h>
#include <wlr/util/box.h>
struct kiwmi_output {
struct wl_list link;
struct kiwmi_desktop *desktop;
struct wlr_output *wlr_output;
struct wl_listener frame;
struct wl_listener commit;
struct wl_listener destroy;
struct wl_listener mode;
struct wl_list layers[4]; // struct kiwmi_layer_surface::link
struct wlr_box usable_area;
int damaged;
struct {
struct wl_signal destroy;
struct wl_signal resize;
struct wl_signal usable_area_change;
} events;
};
struct kiwmi_render_data {
struct wlr_output *output;
double output_lx;
double output_ly;
struct wlr_renderer *renderer;
struct timespec *when;
void *data;
};
void new_output_notify(struct wl_listener *listener, void *data);
void output_damage(struct kiwmi_output *output);
#endif /* KIWMI_DESKTOP_OUTPUT_H */

View File

@ -0,0 +1,184 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_DESKTOP_VIEW_H
#define KIWMI_DESKTOP_VIEW_H
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <wayland-server.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/edges.h>
enum kiwmi_view_prop {
KIWMI_VIEW_PROP_APP_ID,
KIWMI_VIEW_PROP_TITLE,
};
enum kiwmi_view_type {
KIWMI_VIEW_XDG_SHELL,
};
struct kiwmi_view {
struct wl_list link;
struct wl_list children; // struct kiwmi_view_child::link
struct kiwmi_desktop *desktop;
const struct kiwmi_view_impl *impl;
enum kiwmi_view_type type;
union {
struct wlr_xdg_surface *xdg_surface;
};
struct wlr_surface *wlr_surface;
struct wlr_box geom;
struct wl_listener map;
struct wl_listener unmap;
struct wl_listener commit;
struct wl_listener destroy;
struct wl_listener new_popup;
struct wl_listener new_subsurface;
struct wl_listener request_move;
struct wl_listener request_resize;
int x;
int y;
bool mapped;
bool hidden;
struct {
struct wl_signal unmap;
struct wl_signal request_move;
struct wl_signal request_resize;
struct wl_signal post_render;
struct wl_signal pre_render;
} events;
struct kiwmi_xdg_decoration *decoration;
};
struct kiwmi_view_impl {
void (*close)(struct kiwmi_view *view);
void (*for_each_surface)(
struct kiwmi_view *view,
wlr_surface_iterator_func_t callback,
void *user_data);
pid_t (*get_pid)(struct kiwmi_view *view);
void (*set_activated)(struct kiwmi_view *view, bool activated);
void (*set_size)(struct kiwmi_view *view, uint32_t width, uint32_t height);
const char *(
*get_string_prop)(struct kiwmi_view *view, enum kiwmi_view_prop prop);
void (*set_tiled)(struct kiwmi_view *view, enum wlr_edges edges);
struct wlr_surface *(*surface_at)(
struct kiwmi_view *view,
double sx,
double sy,
double *sub_x,
double *sub_y);
};
enum kiwmi_view_child_type {
KIWMI_VIEW_CHILD_SUBSURFACE,
KIWMI_VIEW_CHILD_XDG_POPUP,
};
struct kiwmi_view_child {
struct wl_list link;
struct wl_list children; // struct kiwmi_view_child::link
struct kiwmi_view *view;
struct kiwmi_view_child *parent;
enum kiwmi_view_child_type type;
const struct kiwmi_view_child_impl *impl;
struct wlr_surface *wlr_surface;
union {
struct wlr_subsurface *wlr_subsurface;
struct wlr_xdg_popup *wlr_xdg_popup;
};
bool mapped;
struct wl_listener commit;
struct wl_listener map;
struct wl_listener unmap;
struct wl_listener new_popup;
struct wl_listener new_subsurface;
struct wl_listener extension_destroy; // the union'ed object destroy
struct wl_listener surface_destroy; // wlr_surface::events.destroy
};
struct kiwmi_view_child_impl {
void (*reconfigure)(struct kiwmi_view_child *child);
};
struct kiwmi_request_resize_event {
struct kiwmi_view *view;
uint32_t edges;
};
void view_close(struct kiwmi_view *view);
void view_for_each_surface(
struct kiwmi_view *view,
wlr_surface_iterator_func_t callback,
void *user_data);
pid_t view_get_pid(struct kiwmi_view *view);
void view_get_size(struct kiwmi_view *view, uint32_t *width, uint32_t *height);
const char *view_get_app_id(struct kiwmi_view *view);
const char *view_get_title(struct kiwmi_view *view);
void view_set_activated(struct kiwmi_view *view, bool activated);
void view_set_size(struct kiwmi_view *view, uint32_t width, uint32_t height);
void view_set_pos(struct kiwmi_view *view, uint32_t x, uint32_t y);
void view_set_tiled(struct kiwmi_view *view, enum wlr_edges edges);
struct wlr_surface *view_surface_at(
struct kiwmi_view *view,
double sx,
double sy,
double *sub_x,
double *sub_y);
void view_focus(struct kiwmi_view *view);
struct kiwmi_view *view_at(
struct kiwmi_desktop *desktop,
double lx,
double ly,
struct wlr_surface **surface,
double *sx,
double *sy);
void view_move(struct kiwmi_view *view);
void view_resize(struct kiwmi_view *view, uint32_t edges);
struct kiwmi_view *view_create(
struct kiwmi_desktop *desktop,
enum kiwmi_view_type type,
const struct kiwmi_view_impl *impl);
void
view_init_subsurfaces(struct kiwmi_view_child *child, struct kiwmi_view *view);
bool view_child_is_mapped(struct kiwmi_view_child *child);
void view_child_damage(struct kiwmi_view_child *child);
void view_child_destroy(struct kiwmi_view_child *child);
struct kiwmi_view_child *view_child_create(
struct kiwmi_view_child *parent,
struct kiwmi_view *view,
struct wlr_surface *wlr_surface,
enum kiwmi_view_child_type type,
const struct kiwmi_view_child_impl *impl);
struct kiwmi_view_child *view_child_subsurface_create(
struct kiwmi_view_child *parent,
struct kiwmi_view *view,
struct wlr_subsurface *subsurface);
#endif /* KIWMI_DESKTOP_VIEW_H */

View File

@ -0,0 +1,25 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_DESKTOP_XDG_SHELL_H
#define KIWMI_DESKTOP_XDG_SHELL_H
#include <wayland-server.h>
struct kiwmi_xdg_decoration {
struct kiwmi_view *view;
struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration;
struct wl_listener destroy;
struct wl_listener request_mode;
};
void xdg_shell_new_surface_notify(struct wl_listener *listener, void *data);
void
xdg_toplevel_new_decoration_notify(struct wl_listener *listener, void *data);
#endif /* KIWMI_DESKTOP_XDG_SHELL_H */

View File

@ -0,0 +1,82 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_INPUT_CURSOR_H
#define KIWMI_INPUT_CURSOR_H
#include <wayland-server.h>
#include <wlr/types/wlr_output_layout.h>
#include "desktop/view.h"
enum kiwmi_cursor_mode {
KIWMI_CURSOR_PASSTHROUGH,
KIWMI_CURSOR_MOVE,
KIWMI_CURSOR_RESIZE,
};
struct kiwmi_cursor {
struct kiwmi_server *server;
struct wlr_cursor *cursor;
struct wlr_xcursor_manager *xcursor_manager;
enum kiwmi_cursor_mode cursor_mode;
struct {
struct kiwmi_view *view;
int orig_x;
int orig_y;
struct wlr_box orig_geom;
uint32_t resize_edges;
} grabbed;
struct wl_listener cursor_motion;
struct wl_listener cursor_motion_absolute;
struct wl_listener cursor_button;
struct wl_listener cursor_axis;
struct wl_listener cursor_frame;
struct {
struct wl_signal button_down;
struct wl_signal button_up;
struct wl_signal destroy;
struct wl_signal motion;
struct wl_signal scroll;
} events;
};
struct kiwmi_cursor_button_event {
struct wlr_event_pointer_button *wlr_event;
bool handled;
};
struct kiwmi_cursor_motion_event {
double oldx;
double oldy;
double newx;
double newy;
};
struct kiwmi_cursor_scroll_event {
const char *device_name;
bool is_vertical;
double length;
bool handled;
};
void cursor_refresh_focus(
struct kiwmi_cursor *cursor,
struct wlr_surface **new_surface,
double *cursor_sx,
double *cursor_sy);
struct kiwmi_cursor *cursor_create(
struct kiwmi_server *server,
struct wlr_output_layout *output_layout);
void cursor_destroy(struct kiwmi_cursor *cursor);
#endif /* KIWMI_INPUT_CURSOR_H */

View File

@ -0,0 +1,27 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_INPUT_INPUT_H
#define KIWMI_INPUT_INPUT_H
#include <wayland-server.h>
struct kiwmi_input {
struct wl_list keyboards; // struct kiwmi_keyboard::link
struct wl_listener new_input;
struct kiwmi_cursor *cursor;
struct kiwmi_seat *seat;
struct {
struct wl_signal keyboard_new;
} events;
};
bool input_init(struct kiwmi_input *input);
void input_fini(struct kiwmi_input *input);
#endif /* KIWMI_INPUT_INPUT_H */

View File

@ -0,0 +1,45 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_INPUT_KEYBOARD_H
#define KIWMI_INPUT_KEYBOARD_H
#include <stdint.h>
#include <wayland-server.h>
#include <xkbcommon/xkbcommon.h>
struct kiwmi_keyboard {
struct wl_list link;
struct kiwmi_server *server;
struct wlr_input_device *device;
struct wl_listener modifiers;
struct wl_listener key;
struct wl_listener device_destroy;
struct {
struct wl_signal key_down;
struct wl_signal key_up;
struct wl_signal destroy;
} events;
};
struct kiwmi_keyboard_key_event {
const xkb_keysym_t *raw_syms;
const xkb_keysym_t *translated_syms;
int raw_syms_len;
int translated_syms_len;
uint32_t keycode;
struct kiwmi_keyboard *keyboard;
bool handled;
};
struct kiwmi_keyboard *
keyboard_create(struct kiwmi_server *server, struct wlr_input_device *device);
void keyboard_destroy(struct kiwmi_keyboard *keyboard);
#endif /* KIWMI_INPUT_KEYBOARD_H */

View File

@ -0,0 +1,38 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_INPUT_SEAT_H
#define KIWMI_INPUT_SEAT_H
#include <wayland-server.h>
#include <wlr/types/wlr_surface.h>
#include "desktop/layer_shell.h"
#include "desktop/view.h"
#include "input/input.h"
struct kiwmi_seat {
struct kiwmi_input *input;
struct wlr_seat *seat;
struct kiwmi_view *focused_view;
struct kiwmi_layer *focused_layer;
struct wl_listener request_set_cursor;
struct wl_listener request_set_selection;
struct wl_listener request_set_primary_selection;
};
void
seat_focus_surface(struct kiwmi_seat *seat, struct wlr_surface *wlr_surface);
void seat_focus_layer(struct kiwmi_seat *seat, struct kiwmi_layer *layer);
void seat_focus_view(struct kiwmi_seat *seat, struct kiwmi_view *view);
struct kiwmi_seat *seat_create(struct kiwmi_input *input);
void seat_destroy(struct kiwmi_seat *seat);
#endif /* KIWMI_INPUT_SEAT_H */

18
kiwmi/include/luak/ipc.h Normal file
View File

@ -0,0 +1,18 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_LUAK_IPC_H
#define KIWMI_LUAK_IPC_H
#include <stdbool.h>
#include "luak/luak.h"
#include "server.h"
bool luaK_ipc_init(struct kiwmi_server *server, struct kiwmi_lua *lua);
#endif /* KIWMI_LUAK_IPC_H */

View File

@ -0,0 +1,16 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_LUAK_KIWMI_CURSOR_H
#define KIWMI_LUAK_KIWMI_CURSOR_H
#include <lua.h>
int luaK_kiwmi_cursor_new(lua_State *L);
int luaK_kiwmi_cursor_register(lua_State *L);
#endif /* KIWMI_LUAK_KIWMI_CURSOR_H */

View File

@ -0,0 +1,16 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_LUAK_KIWMI_KEYBOARD_H
#define KIWMI_LUAK_KIWMI_KEYBOARD_H
#include <lua.h>
int luaK_kiwmi_keyboard_new(lua_State *L);
int luaK_kiwmi_keyboard_register(lua_State *L);
#endif /* KIWMI_LUAK_KIWMI_KEYBOARD_H */

View File

@ -0,0 +1,27 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_LUAK_KIWMI_LUA_CALLBACK_H
#define KIWMI_LUAK_KIWMI_LUA_CALLBACK_H
#include <lua.h>
#include "luak/luak.h"
struct kiwmi_lua_callback {
struct wl_list link;
struct kiwmi_server *server;
int callback_ref;
union {
struct wl_event_source *event_source;
struct wl_listener listener;
};
};
int luaK_kiwmi_lua_callback_new(lua_State *L);
#endif /* KIWMI_LUAK_KIWMI_LUA_CALLBACK_H */

View File

@ -0,0 +1,16 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_LUAK_KIWMI_OUTPUT_H
#define KIWMI_LUAK_KIWMI_OUTPUT_H
#include <lua.h>
int luaK_kiwmi_output_new(lua_State *L);
int luaK_kiwmi_output_register(lua_State *L);
#endif /* KIWMI_LUAK_KIWMI_OUTPUT_H */

View File

@ -0,0 +1,16 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_LUAK_KIWMI_RENDERER_H
#define KIWMI_LUAK_KIWMI_RENDERER_H
#include <lua.h>
int luaK_kiwmi_renderer_new(lua_State *L);
int luaK_kiwmi_renderer_register(lua_State *L);
#endif /* KIWMI_LUAK_KIWMI_RENDERER_H */

View File

@ -0,0 +1,16 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_LUAK_KIWMI_SERVER_H
#define KIWMI_LUAK_KIWMI_SERVER_H
#include <lua.h>
int luaK_kiwmi_server_new(lua_State *L);
int luaK_kiwmi_server_register(lua_State *L);
#endif /* KIWMI_LUAK_KIWMI_SERVER_H */

View File

@ -0,0 +1,16 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_LUAK_KIWMI_VIEW_H
#define KIWMI_LUAK_KIWMI_VIEW_H
#include <lua.h>
int luaK_kiwmi_view_new(lua_State *L);
int luaK_kiwmi_view_register(lua_State *L);
#endif /* KIWMI_LUAK_KIWMI_VIEW_H */

View File

@ -0,0 +1,21 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_LUAK_LUA_COMPAT_H
#define KIWMI_LUAK_LUA_COMPAT_H
#include <lauxlib.h>
#include <lua.h>
#define luaC_newlibtable(L, l) \
lua_createtable(L, 0, sizeof(l) / sizeof((l)[0]) - 1)
#define luaC_newlib(L, l) (luaC_newlibtable(L, l), luaC_setfuncs(L, l, 0))
void luaC_setfuncs(lua_State *L, const luaL_Reg *l, int nup);
#endif /* KIWMI_LUAK_LUA_COMPAT_H */

52
kiwmi/include/luak/luak.h Normal file
View File

@ -0,0 +1,52 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_LUAK_LUAK_H
#define KIWMI_LUAK_LUAK_H
#include <stdbool.h>
#include <lua.h>
#include <wayland-server.h>
#include "server.h"
struct kiwmi_lua {
lua_State *L;
int objects;
struct wl_list scheduled_callbacks;
struct wl_global *global;
};
struct kiwmi_object {
struct kiwmi_lua *lua;
void *object;
size_t refcount;
bool valid;
struct wl_listener destroy;
struct wl_list callbacks; // struct kiwmi_lua_callback::link
struct {
struct wl_signal destroy;
} events;
};
void *luaK_toudata(lua_State *L, int ud, const char *tname);
int luaK_kiwmi_object_gc(lua_State *L);
struct kiwmi_object *luaK_get_kiwmi_object(
struct kiwmi_lua *lua,
void *ptr,
struct wl_signal *destroy);
int luaK_callback_register_dispatch(lua_State *L);
int luaK_usertype_ref_equal(lua_State *L);
struct kiwmi_lua *luaK_create(struct kiwmi_server *server);
bool luaK_dofile(struct kiwmi_lua *lua, const char *config_path);
void luaK_destroy(struct kiwmi_lua *lua);
#endif /* KIWMI_LUAK_LUAK_H */

38
kiwmi/include/server.h Normal file
View File

@ -0,0 +1,38 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#ifndef KIWMI_SERVER_H
#define KIWMI_SERVER_H
#include <stdbool.h>
#include "desktop/desktop.h"
#include "input/input.h"
struct kiwmi_server {
struct wl_display *wl_display;
struct wl_event_loop *wl_event_loop;
struct wlr_backend *backend;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
const char *socket;
char *config_path;
struct kiwmi_lua *lua;
struct kiwmi_desktop desktop;
struct kiwmi_input input;
struct {
struct wl_signal destroy;
} events;
};
bool server_init(struct kiwmi_server *server, char *config_path);
bool server_run(struct kiwmi_server *server);
void server_fini(struct kiwmi_server *server);
#endif /* KIWMI_SERVER_H */

5
kiwmi/kiwmi.desktop Normal file
View File

@ -0,0 +1,5 @@
[Desktop Entry]
Name=kiwmi
Comment=A fully programmable Wayland Compositor
Exec=kiwmi
Type=Application

42
kiwmi/kiwmi/color.c Normal file
View File

@ -0,0 +1,42 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "color.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
bool
color_parse(const char *hex, float color[static 4])
{
if (hex[0] == '#') {
++hex;
}
int len = strlen(hex);
if (len != 6 && len != 8) {
return false;
}
uint32_t rgba = (uint32_t)strtoul(hex, NULL, 16);
if (len == 6) {
rgba = (rgba << 8) | 0xff;
}
for (size_t i = 0; i < 4; ++i) {
color[3 - i] = (rgba & 0xff) / 255.0;
rgba >>= 8;
}
// premultiply alpha
color[0] *= color[3];
color[1] *= color[3];
color[2] *= color[3];
return true;
}

View File

@ -0,0 +1,125 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "desktop/desktop.h"
#include <stdbool.h>
#include <wayland-server.h>
#include <wlr/backend.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_export_dmabuf_v1.h>
#include <wlr/types/wlr_layer_shell_v1.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/types/wlr_xdg_output_v1.h>
#include <wlr/types/wlr_xdg_shell.h>
#include "desktop/layer_shell.h"
#include "desktop/output.h"
#include "desktop/view.h"
#include "desktop/xdg_shell.h"
#include "input/cursor.h"
#include "input/input.h"
#include "server.h"
bool
desktop_init(struct kiwmi_desktop *desktop)
{
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
desktop->compositor =
wlr_compositor_create(server->wl_display, server->renderer);
desktop->data_device_manager =
wlr_data_device_manager_create(server->wl_display);
desktop->output_layout = wlr_output_layout_create();
wlr_export_dmabuf_manager_v1_create(server->wl_display);
wlr_xdg_output_manager_v1_create(
server->wl_display, desktop->output_layout);
desktop->bg_color[0] = 0.1f;
desktop->bg_color[1] = 0.1f;
desktop->bg_color[2] = 0.1f;
desktop->bg_color[3] = 1.0f;
desktop->xdg_shell = wlr_xdg_shell_create(server->wl_display);
desktop->xdg_shell_new_surface.notify = xdg_shell_new_surface_notify;
wl_signal_add(
&desktop->xdg_shell->events.new_surface,
&desktop->xdg_shell_new_surface);
desktop->xdg_decoration_manager =
wlr_xdg_decoration_manager_v1_create(server->wl_display);
desktop->xdg_toplevel_new_decoration.notify =
xdg_toplevel_new_decoration_notify;
wl_signal_add(
&desktop->xdg_decoration_manager->events.new_toplevel_decoration,
&desktop->xdg_toplevel_new_decoration);
desktop->layer_shell = wlr_layer_shell_v1_create(server->wl_display);
desktop->layer_shell_new_surface.notify = layer_shell_new_surface_notify;
wl_signal_add(
&desktop->layer_shell->events.new_surface,
&desktop->layer_shell_new_surface);
wl_list_init(&desktop->outputs);
wl_list_init(&desktop->views);
desktop->new_output.notify = new_output_notify;
wl_signal_add(&server->backend->events.new_output, &desktop->new_output);
wl_signal_init(&desktop->events.new_output);
wl_signal_init(&desktop->events.view_map);
wl_signal_init(&desktop->events.request_active_output);
return true;
}
void
desktop_fini(struct kiwmi_desktop *desktop)
{
wlr_output_layout_destroy(desktop->output_layout);
desktop->output_layout = NULL;
}
struct kiwmi_output *
desktop_active_output(struct kiwmi_server *server)
{
// 1. callback (request_active_output)
struct kiwmi_output *output = NULL;
wl_signal_emit(&server->desktop.events.request_active_output, &output);
if (output) {
return output;
}
// 2. focused view center
if (!wl_list_empty(&server->desktop.views)) {
struct kiwmi_view *view;
wl_list_for_each (view, &server->desktop.views, link) {
break; // get first element of list
}
double lx = view->x + view->geom.width / 2;
double ly = view->y + view->geom.height / 2;
struct wlr_output *wlr_output =
wlr_output_layout_output_at(server->desktop.output_layout, lx, ly);
return wlr_output->data;
}
// 3. cursor
double lx = server->input.cursor->cursor->x;
double ly = server->input.cursor->cursor->y;
struct wlr_output *wlr_output =
wlr_output_layout_output_at(server->desktop.output_layout, lx, ly);
return wlr_output->data;
}

View File

@ -0,0 +1,436 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "desktop/layer_shell.h"
#include <stdlib.h>
#include <pixman.h>
#include <wayland-server.h>
#include <wlr/types/wlr_layer_shell_v1.h>
#include <wlr/util/log.h>
#include "desktop/desktop.h"
#include "desktop/output.h"
#include "input/seat.h"
#include "server.h"
static void
kiwmi_layer_destroy_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_layer *layer = wl_container_of(listener, layer, destroy);
wl_list_remove(&layer->link);
wl_list_remove(&layer->destroy.link);
wl_list_remove(&layer->map.link);
wl_list_remove(&layer->unmap.link);
arrange_layers(layer->output);
free(layer);
}
static void
kiwmi_layer_commit_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_layer *layer = wl_container_of(listener, layer, commit);
struct kiwmi_output *output = layer->output;
struct wlr_box old_geom = layer->geom;
if (layer->layer_surface->current.committed != 0) {
arrange_layers(output);
}
bool layer_changed = layer->layer != layer->layer_surface->current.layer;
bool geom_changed = memcmp(&old_geom, &layer->geom, sizeof(old_geom)) != 0;
bool buffer_changed = pixman_region32_not_empty(
&layer->layer_surface->surface->buffer_damage);
if (layer_changed) {
wl_list_remove(&layer->link);
layer->layer = layer->layer_surface->current.layer;
wl_list_insert(&output->layers[layer->layer], &layer->link);
}
if (buffer_changed || layer_changed || geom_changed) {
output_damage(layer->output);
}
}
static void
kiwmi_layer_map_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_layer *layer = wl_container_of(listener, layer, map);
output_damage(layer->output);
}
static void
kiwmi_layer_unmap_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_layer *layer = wl_container_of(listener, layer, unmap);
output_damage(layer->output);
}
static void
apply_exclusive(
struct wlr_box *usable_area,
uint32_t anchor,
int32_t exclusive,
int32_t margin_top,
int32_t margin_bottom,
int32_t margin_left,
int32_t margin_right)
{
if (exclusive <= 0) {
return;
}
struct {
uint32_t anchors;
int *positive_axis;
int *negative_axis;
int margin;
} edges[] = {
{
.anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
| ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT
| ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP,
.positive_axis = &usable_area->y,
.negative_axis = &usable_area->height,
.margin = margin_top,
},
{
.anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
| ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT
| ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
.positive_axis = NULL,
.negative_axis = &usable_area->height,
.margin = margin_bottom,
},
{
.anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
| ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
| ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
.positive_axis = &usable_area->x,
.negative_axis = &usable_area->width,
.margin = margin_left,
},
{
.anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT
| ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
| ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
.positive_axis = NULL,
.negative_axis = &usable_area->width,
.margin = margin_right,
},
};
size_t nedges = sizeof(edges) / sizeof(edges[0]);
for (size_t i = 0; i < nedges; ++i) {
if ((anchor & edges[i].anchors) == edges[i].anchors
&& exclusive + edges[i].margin > 0) {
if (edges[i].positive_axis) {
*edges[i].positive_axis += exclusive + edges[i].margin;
}
if (edges[i].negative_axis) {
*edges[i].negative_axis -= exclusive + edges[i].margin;
}
}
}
}
static void
arrange_layer(
struct kiwmi_output *output,
struct wl_list *layers,
struct wlr_box *usable_area,
bool exclusive)
{
struct wlr_box full_area = {0};
wlr_output_effective_resolution(
output->wlr_output, &full_area.width, &full_area.height);
struct kiwmi_layer *layer;
wl_list_for_each_reverse (layer, layers, link) {
struct wlr_layer_surface_v1 *layer_surface = layer->layer_surface;
struct wlr_layer_surface_v1_state *state = &layer_surface->current;
if (exclusive != (state->exclusive_zone >= 0)) {
continue;
}
struct wlr_box bounds;
if (state->exclusive_zone == -1) {
bounds = full_area;
} else {
bounds = *usable_area;
}
struct wlr_box arranged_area = {
.width = state->desired_width,
.height = state->desired_height,
};
// horizontal
const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
| ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
if ((state->anchor & both_horiz) && arranged_area.width == 0) {
arranged_area.x = bounds.x;
arranged_area.width = bounds.width;
} else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) {
arranged_area.x = bounds.x;
} else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) {
arranged_area.x = bounds.x + (bounds.width - arranged_area.width);
} else {
arranged_area.x =
bounds.x + ((bounds.width / 2) - (arranged_area.width / 2));
}
// vertical
const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
| ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
if ((state->anchor & both_vert) && arranged_area.height == 0) {
arranged_area.y = bounds.y;
arranged_area.height = bounds.height;
} else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) {
arranged_area.y = bounds.y;
} else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) {
arranged_area.y = bounds.y + (bounds.height - arranged_area.height);
} else {
arranged_area.y =
bounds.y + ((bounds.height / 2) - (arranged_area.height / 2));
}
// left and right margin
if ((state->anchor & both_horiz) == both_horiz) {
arranged_area.x += state->margin.left;
arranged_area.width -= state->margin.left + state->margin.right;
} else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) {
arranged_area.x += state->margin.left;
} else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) {
arranged_area.x -= state->margin.right;
}
// top and bottom margin
if ((state->anchor & both_vert) == both_vert) {
arranged_area.y += state->margin.top;
arranged_area.height -= state->margin.top + state->margin.bottom;
} else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) {
arranged_area.y += state->margin.top;
} else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) {
arranged_area.y -= state->margin.bottom;
}
if (arranged_area.width < 0 || arranged_area.height < 0) {
wlr_log(
WLR_ERROR,
"Bad width/height: %d, %d",
arranged_area.width,
arranged_area.height);
wlr_layer_surface_v1_destroy(layer_surface);
continue;
}
layer->geom = arranged_area;
apply_exclusive(
usable_area,
state->anchor,
state->exclusive_zone,
state->margin.top,
state->margin.bottom,
state->margin.left,
state->margin.right);
wlr_layer_surface_v1_configure(
layer_surface, arranged_area.width, arranged_area.height);
}
}
void
arrange_layers(struct kiwmi_output *output)
{
struct wlr_box usable_area = {0};
wlr_output_effective_resolution(
output->wlr_output, &usable_area.width, &usable_area.height);
// arrange exclusive layers
arrange_layer(
output,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
&usable_area,
true);
arrange_layer(
output,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
&usable_area,
true);
arrange_layer(
output,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
&usable_area,
true);
arrange_layer(
output,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
&usable_area,
true);
// arrange non-exclusive layers
arrange_layer(
output,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
&usable_area,
false);
arrange_layer(
output,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
&usable_area,
false);
arrange_layer(
output,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
&usable_area,
false);
arrange_layer(
output,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
&usable_area,
false);
uint32_t layers_above_shell[] = {
ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
ZWLR_LAYER_SHELL_V1_LAYER_TOP,
};
size_t nlayers = sizeof(layers_above_shell) / sizeof(layers_above_shell[0]);
struct kiwmi_layer *layer;
struct kiwmi_layer *topmost = NULL;
for (size_t i = 0; i < nlayers; ++i) {
wl_list_for_each_reverse (
layer, &output->layers[layers_above_shell[i]], link) {
if (layer->layer_surface->current.keyboard_interactive) {
topmost = layer;
break;
}
}
if (topmost) {
break;
}
}
if (memcmp(&usable_area, &output->usable_area, sizeof(output->usable_area))
!= 0) {
memcpy(&output->usable_area, &usable_area, sizeof(output->usable_area));
wl_signal_emit(&output->events.usable_area_change, output);
}
struct kiwmi_desktop *desktop = output->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
struct kiwmi_seat *seat = server->input.seat;
seat_focus_layer(seat, topmost);
}
struct kiwmi_layer *
layer_at(
struct wl_list *layers,
struct wlr_surface **surface,
double ox,
double oy,
double *sx,
double *sy)
{
struct kiwmi_layer *layer;
wl_list_for_each_reverse (layer, layers, link) {
double layer_sx = ox - layer->geom.x;
double layer_sy = oy - layer->geom.y;
double _sx;
double _sy;
struct wlr_surface *_surface = wlr_layer_surface_v1_surface_at(
layer->layer_surface, layer_sx, layer_sy, &_sx, &_sy);
if (_surface) {
*sx = _sx;
*sy = _sy;
*surface = _surface;
return layer;
}
}
return NULL;
}
void
layer_shell_new_surface_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_desktop *desktop =
wl_container_of(listener, desktop, layer_shell_new_surface);
struct wlr_layer_surface_v1 *layer_surface = data;
wlr_log(
WLR_DEBUG,
"New layer_shell surface namespace='%s'",
layer_surface->namespace);
if (!layer_surface->output) {
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
layer_surface->output = desktop_active_output(server)->wlr_output;
}
struct kiwmi_layer *layer = malloc(sizeof(*layer));
if (!layer) {
wlr_log(WLR_ERROR, "Failed too allocate kiwmi_layer_shell");
return;
}
struct kiwmi_output *output = layer_surface->output->data;
size_t len = sizeof(output->layers) / sizeof(output->layers[0]);
if (layer_surface->current.layer >= len) {
wlr_log(
WLR_ERROR,
"Bad layer surface layer '%d'",
layer_surface->current.layer);
wlr_layer_surface_v1_destroy(layer_surface);
free(layer);
return;
}
layer->layer_surface = layer_surface;
layer->output = output;
layer->layer = layer_surface->current.layer;
layer->destroy.notify = kiwmi_layer_destroy_notify;
wl_signal_add(&layer_surface->events.destroy, &layer->destroy);
layer->commit.notify = kiwmi_layer_commit_notify;
wl_signal_add(&layer_surface->surface->events.commit, &layer->commit);
layer->map.notify = kiwmi_layer_map_notify;
wl_signal_add(&layer_surface->events.map, &layer->map);
layer->unmap.notify = kiwmi_layer_unmap_notify;
wl_signal_add(&layer_surface->events.unmap, &layer->unmap);
wl_list_insert(&output->layers[layer->layer], &layer->link);
// Temporarily set the layer's current state to pending
// So that we can easily arrange it
struct wlr_layer_surface_v1_state old_state = layer_surface->current;
layer_surface->current = layer_surface->pending;
arrange_layers(output);
layer_surface->current = old_state;
}

View File

@ -0,0 +1,383 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "desktop/output.h"
#include <stdlib.h>
#include <pixman.h>
#include <wayland-server.h>
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_layer_shell_v1.h>
#include <wlr/types/wlr_matrix.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_surface.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/util/log.h>
#include "desktop/desktop.h"
#include "desktop/layer_shell.h"
#include "desktop/view.h"
#include "input/cursor.h"
#include "input/input.h"
#include "server.h"
static void
render_layer_surface(struct wlr_surface *surface, int x, int y, void *data)
{
struct kiwmi_render_data *rdata = data;
struct wlr_output *wlr_output = rdata->output;
struct wlr_box *geom = rdata->data;
struct wlr_texture *texture = wlr_surface_get_texture(surface);
if (!texture) {
return;
}
int ox = x + geom->x;
int oy = y + geom->y;
struct wlr_box box = {
.x = ox * wlr_output->scale,
.y = oy * wlr_output->scale,
.width = surface->current.width * wlr_output->scale,
.height = surface->current.height * wlr_output->scale,
};
float matrix[9];
enum wl_output_transform transform =
wlr_output_transform_invert(surface->current.transform);
wlr_matrix_project_box(
matrix, &box, transform, 0, wlr_output->transform_matrix);
wlr_render_texture_with_matrix(rdata->renderer, texture, matrix, 1);
wlr_surface_send_frame_done(surface, rdata->when);
}
static void
render_layer(struct wl_list *layer, struct kiwmi_render_data *rdata)
{
struct kiwmi_layer *surface;
wl_list_for_each (surface, layer, link) {
rdata->data = &surface->geom;
wlr_layer_surface_v1_for_each_surface(
surface->layer_surface, render_layer_surface, rdata);
}
}
static void
send_frame_done_to_layer_surface(
struct wlr_surface *surface,
int UNUSED(x),
int UNUSED(y),
void *data)
{
struct timespec *now = data;
wlr_surface_send_frame_done(surface, now);
}
static void
send_frame_done_to_layer(struct wl_list *layer, struct timespec *now)
{
struct kiwmi_layer *surface;
wl_list_for_each (surface, layer, link) {
wlr_layer_surface_v1_for_each_surface(
surface->layer_surface, send_frame_done_to_layer_surface, now);
}
}
static void
render_surface(struct wlr_surface *surface, int sx, int sy, void *data)
{
struct kiwmi_render_data *rdata = data;
struct kiwmi_view *view = rdata->data;
struct wlr_output *wlr_output = rdata->output;
struct wlr_texture *texture = wlr_surface_get_texture(surface);
if (!texture) {
return;
}
int ox = rdata->output_lx + sx + view->x - view->geom.x;
int oy = rdata->output_ly + sy + view->y - view->geom.y;
struct wlr_box box = {
.x = ox * wlr_output->scale,
.y = oy * wlr_output->scale,
.width = surface->current.width * wlr_output->scale,
.height = surface->current.height * wlr_output->scale,
};
float matrix[9];
enum wl_output_transform transform =
wlr_output_transform_invert(surface->current.transform);
wlr_matrix_project_box(
matrix, &box, transform, 0, wlr_output->transform_matrix);
wlr_render_texture_with_matrix(rdata->renderer, texture, matrix, 1);
wlr_surface_send_frame_done(surface, rdata->when);
}
static void
send_frame_done_to_surface(
struct wlr_surface *surface,
int UNUSED(sx),
int UNUSED(sy),
void *data)
{
struct timespec *now = data;
wlr_surface_send_frame_done(surface, now);
}
static bool
render_cursors(struct wlr_output *wlr_output)
{
pixman_region32_t damage;
pixman_region32_init(&damage);
wlr_output_render_software_cursors(wlr_output, &damage);
bool damaged = pixman_region32_not_empty(&damage);
pixman_region32_fini(&damage);
return damaged;
}
static void
output_frame_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_output *output = wl_container_of(listener, output, frame);
struct wlr_output *wlr_output = data;
struct kiwmi_desktop *desktop = output->desktop;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
int buffer_age;
if (!wlr_output_attach_render(wlr_output, &buffer_age)) {
wlr_log(WLR_ERROR, "Failed to attach renderer to output");
return;
}
if (output->damaged == 0 && buffer_age > 0) {
send_frame_done_to_layer(
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &now);
send_frame_done_to_layer(
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now);
send_frame_done_to_layer(
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &now);
send_frame_done_to_layer(
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &now);
struct kiwmi_view *view;
wl_list_for_each (view, &desktop->views, link) {
view_for_each_surface(view, send_frame_done_to_surface, &now);
}
if (render_cursors(wlr_output)) {
output_damage(output);
}
wlr_output_commit(wlr_output);
return;
}
struct wlr_output_layout *output_layout = desktop->output_layout;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
struct wlr_renderer *renderer = server->renderer;
int width;
int height;
wlr_output_effective_resolution(wlr_output, &width, &height);
wlr_renderer_begin(renderer, width, height);
wlr_renderer_clear(renderer, desktop->bg_color);
double output_lx = 0;
double output_ly = 0;
wlr_output_layout_output_coords(
output_layout, wlr_output, &output_lx, &output_ly);
struct kiwmi_render_data rdata = {
.output = output->wlr_output,
.output_lx = output_lx,
.output_ly = output_ly,
.renderer = renderer,
.when = &now,
};
render_layer(&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &rdata);
render_layer(&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &rdata);
struct kiwmi_view *view;
wl_list_for_each_reverse (view, &desktop->views, link) {
if (view->hidden || !view->mapped) {
continue;
}
rdata.data = view;
wl_signal_emit(&view->events.pre_render, &rdata);
view_for_each_surface(view, render_surface, &rdata);
wl_signal_emit(&view->events.post_render, &rdata);
}
render_layer(&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &rdata);
render_layer(&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &rdata);
bool damaged = render_cursors(wlr_output);
wlr_renderer_end(renderer);
if (damaged) {
output_damage(output);
} else {
--output->damaged;
}
wlr_output_commit(wlr_output);
}
static void
output_commit_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_output *output = wl_container_of(listener, output, commit);
struct wlr_output_event_commit *event = data;
if (event->committed & WLR_OUTPUT_STATE_TRANSFORM) {
arrange_layers(output);
output_damage(output);
wl_signal_emit(&output->events.resize, output);
}
}
static void
output_destroy_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_output *output = wl_container_of(listener, output, destroy);
if (output->desktop->output_layout) {
wlr_output_layout_remove(
output->desktop->output_layout, output->wlr_output);
}
wl_signal_emit(&output->events.destroy, output);
wl_list_remove(&output->link);
wl_list_remove(&output->frame.link);
wl_list_remove(&output->commit.link);
wl_list_remove(&output->destroy.link);
wl_list_remove(&output->mode.link);
wl_list_remove(&output->events.destroy.listener_list);
free(output);
}
static void
output_mode_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_output *output = wl_container_of(listener, output, mode);
arrange_layers(output);
output_damage(output);
wl_signal_emit(&output->events.resize, output);
}
static struct kiwmi_output *
output_create(struct wlr_output *wlr_output, struct kiwmi_desktop *desktop)
{
struct kiwmi_output *output = calloc(1, sizeof(*output));
if (!output) {
return NULL;
}
output->wlr_output = wlr_output;
output->desktop = desktop;
output->usable_area.width = wlr_output->width;
output->usable_area.height = wlr_output->height;
output->frame.notify = output_frame_notify;
wl_signal_add(&wlr_output->events.frame, &output->frame);
output->commit.notify = output_commit_notify;
wl_signal_add(&wlr_output->events.commit, &output->commit);
output->destroy.notify = output_destroy_notify;
wl_signal_add(&wlr_output->events.destroy, &output->destroy);
output->mode.notify = output_mode_notify;
wl_signal_add(&wlr_output->events.mode, &output->mode);
output_damage(output);
return output;
}
void
new_output_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_desktop *desktop =
wl_container_of(listener, desktop, new_output);
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
struct wlr_output *wlr_output = data;
wlr_log(WLR_DEBUG, "New output %p: %s", wlr_output, wlr_output->name);
wlr_output_init_render(wlr_output, server->allocator, server->renderer);
struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output);
if (mode) {
wlr_output_set_mode(wlr_output, mode);
if (!wlr_output_commit(wlr_output)) {
wlr_log(WLR_ERROR, "Failed to modeset output");
return;
}
}
struct kiwmi_output *output = output_create(wlr_output, desktop);
if (!output) {
wlr_log(WLR_ERROR, "Failed to create output");
return;
}
wlr_output->data = output;
struct kiwmi_cursor *cursor = server->input.cursor;
wlr_xcursor_manager_load(cursor->xcursor_manager, wlr_output->scale);
wl_list_insert(&desktop->outputs, &output->link);
wlr_output_layout_add_auto(desktop->output_layout, wlr_output);
wlr_output_create_global(wlr_output);
size_t len_outputs = sizeof(output->layers) / sizeof(output->layers[0]);
for (size_t i = 0; i < len_outputs; ++i) {
wl_list_init(&output->layers[i]);
}
wl_signal_init(&output->events.destroy);
wl_signal_init(&output->events.resize);
wl_signal_init(&output->events.usable_area_change);
wl_signal_emit(&desktop->events.new_output, output);
}
void
output_damage(struct kiwmi_output *output)
{
output->damaged = 2;
}

503
kiwmi/kiwmi/desktop/view.c Normal file
View File

@ -0,0 +1,503 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "desktop/view.h"
#include <wlr/types/wlr_cursor.h>
#include <wlr/util/log.h>
#include "desktop/output.h"
#include "input/cursor.h"
#include "input/seat.h"
#include "server.h"
void
view_close(struct kiwmi_view *view)
{
if (view->impl->close) {
view->impl->close(view);
}
}
void
view_for_each_surface(
struct kiwmi_view *view,
wlr_surface_iterator_func_t callback,
void *user_data)
{
if (view->impl->for_each_surface) {
view->impl->for_each_surface(view, callback, user_data);
}
}
pid_t
view_get_pid(struct kiwmi_view *view)
{
if (view->impl->get_pid) {
return view->impl->get_pid(view);
}
return -1;
}
void
view_get_size(struct kiwmi_view *view, uint32_t *width, uint32_t *height)
{
*width = view->geom.width;
*height = view->geom.height;
}
const char *
view_get_app_id(struct kiwmi_view *view)
{
if (view->impl->get_string_prop) {
return view->impl->get_string_prop(view, KIWMI_VIEW_PROP_APP_ID);
}
return NULL;
}
const char *
view_get_title(struct kiwmi_view *view)
{
if (view->impl->get_string_prop) {
return view->impl->get_string_prop(view, KIWMI_VIEW_PROP_TITLE);
}
return NULL;
}
void
view_set_activated(struct kiwmi_view *view, bool activated)
{
if (view->impl->set_activated) {
view->impl->set_activated(view, activated);
}
}
void
view_set_size(struct kiwmi_view *view, uint32_t width, uint32_t height)
{
if (view->impl->set_size) {
view->impl->set_size(view, width, height);
struct kiwmi_view_child *child;
wl_list_for_each (child, &view->children, link) {
if (child->impl && child->impl->reconfigure) {
child->impl->reconfigure(child);
}
}
struct kiwmi_output *output;
wl_list_for_each (output, &view->desktop->outputs, link) {
output_damage(output);
}
}
}
void
view_set_pos(struct kiwmi_view *view, uint32_t x, uint32_t y)
{
view->x = x;
view->y = y;
struct kiwmi_view_child *child;
wl_list_for_each (child, &view->children, link) {
if (child->impl && child->impl->reconfigure) {
child->impl->reconfigure(child);
}
}
struct kiwmi_desktop *desktop = view->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
struct kiwmi_cursor *cursor = server->input.cursor;
cursor_refresh_focus(cursor, NULL, NULL, NULL);
struct kiwmi_output *output;
wl_list_for_each (output, &desktop->outputs, link) {
output_damage(output);
}
}
void
view_set_tiled(struct kiwmi_view *view, enum wlr_edges edges)
{
if (view->impl->set_tiled) {
view->impl->set_tiled(view, edges);
}
}
struct wlr_surface *
view_surface_at(
struct kiwmi_view *view,
double sx,
double sy,
double *sub_x,
double *sub_y)
{
if (view->impl->surface_at) {
return view->impl->surface_at(view, sx, sy, sub_x, sub_y);
}
return NULL;
}
static bool
surface_at(
struct kiwmi_view *view,
struct wlr_surface **surface,
double lx,
double ly,
double *sx,
double *sy)
{
double view_sx = lx - view->x + view->geom.x;
double view_sy = ly - view->y + view->geom.y;
double _sx;
double _sy;
struct wlr_surface *_surface =
view_surface_at(view, view_sx, view_sy, &_sx, &_sy);
if (_surface) {
*sx = _sx;
*sy = _sy;
*surface = _surface;
return true;
}
return false;
}
struct kiwmi_view *
view_at(
struct kiwmi_desktop *desktop,
double lx,
double ly,
struct wlr_surface **surface,
double *sx,
double *sy)
{
struct kiwmi_view *view;
wl_list_for_each (view, &desktop->views, link) {
if (view->hidden || !view->mapped) {
continue;
}
if (surface_at(view, surface, lx, ly, sx, sy)) {
return view;
}
}
return NULL;
}
static void
view_begin_interactive(
struct kiwmi_view *view,
enum kiwmi_cursor_mode mode,
uint32_t edges)
{
struct kiwmi_desktop *desktop = view->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
struct kiwmi_cursor *cursor = server->input.cursor;
struct wlr_surface *focused_surface =
server->input.seat->seat->pointer_state.focused_surface;
struct wlr_surface *wlr_surface = view->wlr_surface;
if (wlr_surface != focused_surface) {
return;
}
uint32_t width;
uint32_t height;
view_get_size(view, &width, &height);
cursor->cursor_mode = mode;
cursor->grabbed.view = view;
if (mode == KIWMI_CURSOR_MOVE) {
cursor->grabbed.orig_x = cursor->cursor->x - view->x;
cursor->grabbed.orig_y = cursor->cursor->y - view->y;
} else {
cursor->grabbed.orig_x = cursor->cursor->x;
cursor->grabbed.orig_y = cursor->cursor->y;
cursor->grabbed.resize_edges = edges;
}
cursor->grabbed.orig_geom.x = view->x;
cursor->grabbed.orig_geom.y = view->y;
cursor->grabbed.orig_geom.width = width;
cursor->grabbed.orig_geom.height = height;
}
void
view_move(struct kiwmi_view *view)
{
view_begin_interactive(view, KIWMI_CURSOR_MOVE, 0);
}
void
view_resize(struct kiwmi_view *view, uint32_t edges)
{
view_begin_interactive(view, KIWMI_CURSOR_RESIZE, edges);
}
/**
* Creates a kiwmi_view_child for each subsurface of either the 'child' or the
* 'view'. 'child' can be NULL, 'view' should never be.
*/
void
view_init_subsurfaces(struct kiwmi_view_child *child, struct kiwmi_view *view)
{
struct wlr_surface *surface =
child ? child->wlr_surface : view->wlr_surface;
if (!surface) {
wlr_log(WLR_ERROR, "Attempting to init_subsurfaces without a surface");
return;
}
struct wlr_subsurface *subsurface;
wl_list_for_each (
subsurface, &surface->current.subsurfaces_below, current.link) {
view_child_subsurface_create(child, view, subsurface);
}
wl_list_for_each (
subsurface, &surface->current.subsurfaces_above, current.link) {
view_child_subsurface_create(child, view, subsurface);
}
}
struct kiwmi_view *
view_create(
struct kiwmi_desktop *desktop,
enum kiwmi_view_type type,
const struct kiwmi_view_impl *impl)
{
struct kiwmi_view *view = malloc(sizeof(*view));
if (!view) {
wlr_log(WLR_ERROR, "Failed to allocate view");
return NULL;
}
view->desktop = desktop;
view->type = type;
view->impl = impl;
view->mapped = false;
view->hidden = true;
view->decoration = NULL;
view->x = 0;
view->y = 0;
wl_list_init(&view->children);
wl_signal_init(&view->events.unmap);
wl_signal_init(&view->events.request_move);
wl_signal_init(&view->events.request_resize);
wl_signal_init(&view->events.post_render);
wl_signal_init(&view->events.pre_render);
return view;
}
bool
view_child_is_mapped(struct kiwmi_view_child *child)
{
if (!child->mapped) {
return false;
}
struct kiwmi_view_child *parent = child->parent;
while (parent) {
if (!parent->mapped) {
return false;
}
parent = parent->parent;
}
return child->view->mapped;
}
void
view_child_damage(struct kiwmi_view_child *child)
{
// Note for later: this is supposed to damage the child and all subchildren
struct kiwmi_output *output;
wl_list_for_each (output, &child->view->desktop->outputs, link) {
output_damage(output);
}
}
void
view_child_destroy(struct kiwmi_view_child *child)
{
bool visible = view_child_is_mapped(child) && !child->view->hidden;
if (visible) {
view_child_damage(child);
}
wl_list_remove(&child->link);
child->parent = NULL;
struct kiwmi_view_child *subchild, *tmpchild;
wl_list_for_each_safe (subchild, tmpchild, &child->children, link) {
subchild->mapped = false;
view_child_destroy(subchild);
}
wl_list_remove(&child->commit.link);
wl_list_remove(&child->map.link);
wl_list_remove(&child->unmap.link);
wl_list_remove(&child->new_popup.link);
wl_list_remove(&child->new_subsurface.link);
wl_list_remove(&child->surface_destroy.link);
wl_list_remove(&child->extension_destroy.link);
free(child);
}
static void
view_child_subsurface_extension_destroy_notify(
struct wl_listener *listener,
void *UNUSED(data))
{
struct kiwmi_view_child *child =
wl_container_of(listener, child, extension_destroy);
view_child_destroy(child);
}
static void
view_child_surface_destroy_notify(
struct wl_listener *listener,
void *UNUSED(data))
{
struct kiwmi_view_child *child =
wl_container_of(listener, child, surface_destroy);
view_child_destroy(child);
}
static void
view_child_commit_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_view_child *child = wl_container_of(listener, child, commit);
if (view_child_is_mapped(child)) {
view_child_damage(child);
}
}
static void
view_child_new_subsurface_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_view_child *child =
wl_container_of(listener, child, new_subsurface);
struct wlr_subsurface *subsurface = data;
view_child_subsurface_create(child, child->view, subsurface);
}
static void
view_child_map_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_view_child *child = wl_container_of(listener, child, map);
child->mapped = true;
if (view_child_is_mapped(child)) {
view_child_damage(child);
}
}
static void
view_child_unmap_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_view_child *child = wl_container_of(listener, child, unmap);
if (view_child_is_mapped(child)) {
view_child_damage(child);
}
child->mapped = false;
}
struct kiwmi_view_child *
view_child_create(
struct kiwmi_view_child *parent,
struct kiwmi_view *view,
struct wlr_surface *wlr_surface,
enum kiwmi_view_child_type type,
const struct kiwmi_view_child_impl *impl)
{
struct kiwmi_view_child *child = calloc(1, sizeof(*child));
if (!child) {
wlr_log(WLR_ERROR, "Failed to allocate view_child");
return NULL;
}
child->type = type;
child->impl = impl;
child->view = view;
child->wlr_surface = wlr_surface;
child->mapped = false;
if (parent) {
child->parent = parent;
wl_list_insert(&parent->children, &child->link);
} else {
wl_list_insert(&view->children, &child->link);
}
wl_list_init(&child->children);
child->commit.notify = view_child_commit_notify;
wl_signal_add(&wlr_surface->events.commit, &child->commit);
child->map.notify = view_child_map_notify;
child->unmap.notify = view_child_unmap_notify;
// wlr_surface doesn't have these events, but its extensions usually do
wl_list_init(&child->map.link);
wl_list_init(&child->unmap.link);
child->new_subsurface.notify = view_child_new_subsurface_notify;
wl_signal_add(&wlr_surface->events.new_subsurface, &child->new_subsurface);
child->surface_destroy.notify = view_child_surface_destroy_notify;
wl_signal_add(&wlr_surface->events.destroy, &child->surface_destroy);
// Possibly unused
wl_list_init(&child->new_popup.link);
wl_list_init(&child->extension_destroy.link);
view_init_subsurfaces(child, child->view);
return child;
}
struct kiwmi_view_child *
view_child_subsurface_create(
struct kiwmi_view_child *parent,
struct kiwmi_view *view,
struct wlr_subsurface *subsurface)
{
struct kiwmi_view_child *child = view_child_create(
parent, view, subsurface->surface, KIWMI_VIEW_CHILD_SUBSURFACE, NULL);
if (!child) {
return NULL;
}
child->wlr_subsurface = subsurface;
child->mapped = subsurface->mapped;
if (view_child_is_mapped(child)) {
view_child_damage(child);
}
wl_signal_add(&subsurface->events.map, &child->map);
wl_signal_add(&subsurface->events.unmap, &child->unmap);
child->extension_destroy.notify =
view_child_subsurface_extension_destroy_notify;
wl_signal_add(&subsurface->events.destroy, &child->extension_destroy);
return child;
}

View File

@ -0,0 +1,498 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "desktop/xdg_shell.h"
#include <unistd.h>
#include <pixman.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/edges.h>
#include <wlr/util/log.h>
#include "desktop/desktop.h"
#include "desktop/output.h"
#include "desktop/view.h"
#include "input/cursor.h"
#include "input/input.h"
#include "input/seat.h"
#include "server.h"
static struct kiwmi_view_child *view_child_popup_create(
struct kiwmi_view_child *parent,
struct kiwmi_view *view,
struct wlr_xdg_popup *wlr_popup);
static void
popup_new_popup_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_view_child *popup =
wl_container_of(listener, popup, new_popup);
struct wlr_xdg_popup *wlr_popup = data;
view_child_popup_create(popup, popup->view, wlr_popup);
}
static void
popup_extension_destroy_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_view_child *popup =
wl_container_of(listener, popup, extension_destroy);
view_child_destroy(popup);
}
static void
popup_unconstrain(struct kiwmi_view_child *popup)
{
if (popup->type != KIWMI_VIEW_CHILD_XDG_POPUP) {
wlr_log(WLR_ERROR, "Expected an xdg_popup kiwmi_view_child");
return;
}
struct kiwmi_view *view = popup->view;
// Prefer output at view center
struct wlr_output *output = wlr_output_layout_output_at(
view->desktop->output_layout,
view->x + view->geom.width / 2,
view->y + view->geom.height / 2);
if (!output) {
// Retry with view top-left corner (if its center is off-screen)
output = wlr_output_layout_output_at(
view->desktop->output_layout, view->x, view->y);
}
if (!output) {
wlr_log(
WLR_ERROR, "View's output not found, popups may end up invisible");
return;
}
double view_ox = view->x;
double view_oy = view->y;
wlr_output_layout_output_coords(
view->desktop->output_layout, output, &view_ox, &view_oy);
int output_width;
int output_height;
wlr_output_effective_resolution(output, &output_width, &output_height);
// relative to the view
struct wlr_box output_box = {
.x = -view_ox,
.y = -view_oy,
.width = output_width,
.height = output_height,
};
wlr_xdg_popup_unconstrain_from_box(popup->wlr_xdg_popup, &output_box);
}
static void
popup_reconfigure(struct kiwmi_view_child *popup)
{
if (popup->type != KIWMI_VIEW_CHILD_XDG_POPUP) {
wlr_log(WLR_ERROR, "Expected an xdg_popup view_child");
return;
}
popup_unconstrain(popup);
struct kiwmi_view_child *subchild;
wl_list_for_each (subchild, &popup->children, link) {
if (subchild->impl && subchild->impl->reconfigure) {
subchild->impl->reconfigure(subchild);
}
}
}
static const struct kiwmi_view_child_impl xdg_popup_view_child_impl = {
.reconfigure = popup_reconfigure,
};
static struct kiwmi_view_child *
view_child_popup_create(
struct kiwmi_view_child *parent,
struct kiwmi_view *view,
struct wlr_xdg_popup *wlr_popup)
{
struct kiwmi_view_child *child = view_child_create(
parent,
view,
wlr_popup->base->surface,
KIWMI_VIEW_CHILD_XDG_POPUP,
&xdg_popup_view_child_impl);
if (!child) {
return NULL;
}
child->wlr_xdg_popup = wlr_popup;
child->mapped = wlr_popup->base->mapped;
if (view_child_is_mapped(child)) {
view_child_damage(child);
}
wl_signal_add(&wlr_popup->base->events.map, &child->map);
wl_signal_add(&wlr_popup->base->events.unmap, &child->unmap);
child->new_popup.notify = popup_new_popup_notify;
wl_signal_add(&wlr_popup->base->events.new_popup, &child->new_popup);
child->extension_destroy.notify = popup_extension_destroy_notify;
wl_signal_add(&wlr_popup->base->events.destroy, &child->extension_destroy);
popup_unconstrain(child);
return child;
}
static void
xdg_surface_new_popup_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_view *view = wl_container_of(listener, view, new_popup);
struct wlr_xdg_popup *wlr_popup = data;
view_child_popup_create(NULL, view, wlr_popup);
}
static void
xdg_surface_new_subsurface_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_view *view = wl_container_of(listener, view, new_subsurface);
struct wlr_subsurface *subsurface = data;
view_child_subsurface_create(NULL, view, subsurface);
}
static void
xdg_surface_map_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_view *view = wl_container_of(listener, view, map);
view->mapped = true;
struct kiwmi_output *output;
wl_list_for_each (output, &view->desktop->outputs, link) {
output_damage(output);
}
wl_signal_emit(&view->desktop->events.view_map, view);
}
static void
xdg_surface_unmap_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_view *view = wl_container_of(listener, view, unmap);
if (view->mapped) {
view->mapped = false;
struct kiwmi_output *output;
wl_list_for_each (output, &view->desktop->outputs, link) {
output_damage(output);
}
wl_signal_emit(&view->events.unmap, view);
}
}
static void
xdg_surface_commit_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_view *view = wl_container_of(listener, view, commit);
struct kiwmi_desktop *desktop = view->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
struct kiwmi_cursor *cursor = server->input.cursor;
cursor_refresh_focus(cursor, NULL, NULL, NULL);
if (pixman_region32_not_empty(&view->wlr_surface->buffer_damage)) {
struct kiwmi_output *output;
wl_list_for_each (output, &desktop->outputs, link) {
output_damage(output);
}
}
wlr_xdg_surface_get_geometry(view->xdg_surface, &view->geom);
}
static void
xdg_surface_destroy_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_view *view = wl_container_of(listener, view, destroy);
struct kiwmi_desktop *desktop = view->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
struct kiwmi_seat *seat = server->input.seat;
if (seat->focused_view == view) {
seat->focused_view = NULL;
}
cursor_refresh_focus(server->input.cursor, NULL, NULL, NULL);
struct kiwmi_view_child *child, *tmpchild;
wl_list_for_each_safe (child, tmpchild, &view->children, link) {
child->mapped = false;
view_child_destroy(child);
}
if (view->decoration) {
view->decoration->view = NULL;
}
wl_list_remove(&view->link);
wl_list_remove(&view->children);
wl_list_remove(&view->map.link);
wl_list_remove(&view->unmap.link);
wl_list_remove(&view->commit.link);
wl_list_remove(&view->destroy.link);
wl_list_remove(&view->new_popup.link);
wl_list_remove(&view->new_subsurface.link);
wl_list_remove(&view->request_move.link);
wl_list_remove(&view->request_resize.link);
wl_list_remove(&view->events.unmap.listener_list);
free(view);
}
static void
xdg_toplevel_request_move_notify(
struct wl_listener *listener,
void *UNUSED(data))
{
struct kiwmi_view *view = wl_container_of(listener, view, request_move);
wl_signal_emit(&view->events.request_move, view);
}
static void
xdg_toplevel_request_resize_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_view *view = wl_container_of(listener, view, request_resize);
struct wlr_xdg_toplevel_resize_event *event = data;
struct kiwmi_request_resize_event new_event = {
.view = view,
.edges = event->edges,
};
wl_signal_emit(&view->events.request_resize, &new_event);
}
static void
xdg_shell_view_close(struct kiwmi_view *view)
{
struct wlr_xdg_surface *surface = view->xdg_surface;
if (surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL && surface->toplevel) {
wlr_xdg_toplevel_send_close(surface);
}
}
static void
xdg_shell_view_for_each_surface(
struct kiwmi_view *view,
wlr_surface_iterator_func_t callback,
void *user_data)
{
wlr_xdg_surface_for_each_surface(view->xdg_surface, callback, user_data);
}
static pid_t
xdg_shell_view_get_pid(struct kiwmi_view *view)
{
struct wl_client *client = view->xdg_surface->client->client;
pid_t pid;
wl_client_get_credentials(client, &pid, NULL, NULL);
return pid;
}
static const char *
xdg_shell_view_get_string_prop(
struct kiwmi_view *view,
enum kiwmi_view_prop prop)
{
switch (prop) {
case KIWMI_VIEW_PROP_APP_ID:
return view->xdg_surface->toplevel->app_id;
case KIWMI_VIEW_PROP_TITLE:
return view->xdg_surface->toplevel->title;
default:
return NULL;
}
}
static void
xdg_shell_view_set_activated(struct kiwmi_view *view, bool activated)
{
wlr_xdg_toplevel_set_activated(view->xdg_surface, activated);
}
static void
xdg_shell_view_set_size(
struct kiwmi_view *view,
uint32_t width,
uint32_t height)
{
wlr_xdg_toplevel_set_size(view->xdg_surface, width, height);
}
static void
xdg_shell_view_set_tiled(struct kiwmi_view *view, enum wlr_edges edges)
{
wlr_xdg_toplevel_set_tiled(view->xdg_surface, edges);
}
struct wlr_surface *
xdg_shell_view_surface_at(
struct kiwmi_view *view,
double sx,
double sy,
double *sub_x,
double *sub_y)
{
return wlr_xdg_surface_surface_at(view->xdg_surface, sx, sy, sub_x, sub_y);
}
static const struct kiwmi_view_impl xdg_shell_view_impl = {
.close = xdg_shell_view_close,
.for_each_surface = xdg_shell_view_for_each_surface,
.get_pid = xdg_shell_view_get_pid,
.get_string_prop = xdg_shell_view_get_string_prop,
.set_activated = xdg_shell_view_set_activated,
.set_size = xdg_shell_view_set_size,
.set_tiled = xdg_shell_view_set_tiled,
.surface_at = xdg_shell_view_surface_at,
};
void
xdg_shell_new_surface_notify(struct wl_listener *listener, void *data)
{
struct wlr_xdg_surface *xdg_surface = data;
struct kiwmi_desktop *desktop =
wl_container_of(listener, desktop, xdg_shell_new_surface);
if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) {
wlr_log(WLR_DEBUG, "New xdg_shell popup");
return;
}
wlr_log(
WLR_DEBUG,
"New xdg_shell toplevel title='%s' app_id='%s'",
xdg_surface->toplevel->title,
xdg_surface->toplevel->app_id);
wlr_xdg_surface_ping(xdg_surface);
struct kiwmi_view *view =
view_create(desktop, KIWMI_VIEW_XDG_SHELL, &xdg_shell_view_impl);
if (!view) {
return;
}
xdg_surface->data = view;
view->xdg_surface = xdg_surface;
view->wlr_surface = xdg_surface->surface;
view->map.notify = xdg_surface_map_notify;
wl_signal_add(&xdg_surface->events.map, &view->map);
view->unmap.notify = xdg_surface_unmap_notify;
wl_signal_add(&xdg_surface->events.unmap, &view->unmap);
view->commit.notify = xdg_surface_commit_notify;
wl_signal_add(&xdg_surface->surface->events.commit, &view->commit);
view->destroy.notify = xdg_surface_destroy_notify;
wl_signal_add(&xdg_surface->events.destroy, &view->destroy);
view->new_popup.notify = xdg_surface_new_popup_notify;
wl_signal_add(&xdg_surface->events.new_popup, &view->new_popup);
view->new_subsurface.notify = xdg_surface_new_subsurface_notify;
wl_signal_add(
&xdg_surface->surface->events.new_subsurface, &view->new_subsurface);
view->request_move.notify = xdg_toplevel_request_move_notify;
wl_signal_add(
&xdg_surface->toplevel->events.request_move, &view->request_move);
view->request_resize.notify = xdg_toplevel_request_resize_notify;
wl_signal_add(
&xdg_surface->toplevel->events.request_resize, &view->request_resize);
view_init_subsurfaces(NULL, view);
wl_list_insert(&desktop->views, &view->link);
}
static void
xdg_decoration_destroy_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_xdg_decoration *decoration =
wl_container_of(listener, decoration, destroy);
if (decoration->view) {
decoration->view->decoration = NULL;
}
wl_list_remove(&decoration->destroy.link);
wl_list_remove(&decoration->request_mode.link);
free(decoration);
}
static void
xdg_decoration_request_mode_notify(
struct wl_listener *listener,
void *UNUSED(data))
{
struct kiwmi_xdg_decoration *decoration =
wl_container_of(listener, decoration, request_mode);
enum wlr_xdg_toplevel_decoration_v1_mode mode =
decoration->wlr_decoration->requested_mode;
if (mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE) {
mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
}
wlr_xdg_toplevel_decoration_v1_set_mode(decoration->wlr_decoration, mode);
}
void
xdg_toplevel_new_decoration_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_desktop *desktop =
wl_container_of(listener, desktop, xdg_toplevel_new_decoration);
struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration = data;
wlr_log(WLR_DEBUG, "New xdg_toplevel decoration");
struct kiwmi_view *view = wlr_decoration->surface->data;
struct kiwmi_xdg_decoration *decoration = malloc(sizeof(*decoration));
if (!decoration) {
wlr_log(WLR_ERROR, "Failed to allocate xdg_decoration");
return;
}
view->decoration = decoration;
decoration->view = view;
decoration->wlr_decoration = wlr_decoration;
decoration->destroy.notify = xdg_decoration_destroy_notify;
wl_signal_add(&wlr_decoration->events.destroy, &decoration->destroy);
decoration->request_mode.notify = xdg_decoration_request_mode_notify;
wl_signal_add(
&wlr_decoration->events.request_mode, &decoration->request_mode);
}

365
kiwmi/kiwmi/input/cursor.c Normal file
View File

@ -0,0 +1,365 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "input/cursor.h"
#include <stdlib.h>
#include <wayland-server.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_layer_shell_v1.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_pointer.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/util/log.h>
#include "desktop/desktop.h"
#include "desktop/layer_shell.h"
#include "desktop/output.h"
#include "desktop/view.h"
#include "input/seat.h"
#include "server.h"
static void
process_cursor_motion(struct kiwmi_server *server, uint32_t time)
{
struct kiwmi_input *input = &server->input;
struct kiwmi_cursor *cursor = input->cursor;
struct wlr_seat *seat = input->seat->seat;
switch (cursor->cursor_mode) {
case KIWMI_CURSOR_MOVE: {
struct kiwmi_view *view = cursor->grabbed.view;
view_set_pos(
view,
cursor->cursor->x - cursor->grabbed.orig_x,
cursor->cursor->y - cursor->grabbed.orig_y);
return;
}
case KIWMI_CURSOR_RESIZE: {
struct kiwmi_view *view = cursor->grabbed.view;
int dx = cursor->cursor->x - cursor->grabbed.orig_x;
int dy = cursor->cursor->y - cursor->grabbed.orig_y;
struct wlr_box new_geom = {
.x = cursor->grabbed.orig_geom.x,
.y = cursor->grabbed.orig_geom.y,
.width = cursor->grabbed.orig_geom.width,
.height = cursor->grabbed.orig_geom.height,
};
if (cursor->grabbed.resize_edges & WLR_EDGE_TOP) {
new_geom.y += dy;
new_geom.height -= dy;
if (new_geom.height < 1) {
new_geom.y += new_geom.height;
}
}
if (cursor->grabbed.resize_edges & WLR_EDGE_BOTTOM) {
new_geom.height += dy;
}
if (cursor->grabbed.resize_edges & WLR_EDGE_LEFT) {
new_geom.x += dx;
new_geom.width -= dx;
if (new_geom.width < 1) {
new_geom.x += new_geom.width;
}
}
if (cursor->grabbed.resize_edges & WLR_EDGE_RIGHT) {
new_geom.width += dx;
}
view_set_pos(view, new_geom.x, new_geom.y);
view_set_size(view, new_geom.width, new_geom.height);
return;
}
default:
// EMPTY
break;
}
struct wlr_surface *old_focus = seat->pointer_state.focused_surface;
struct wlr_surface *new_focus;
double sx, sy;
cursor_refresh_focus(cursor, &new_focus, &sx, &sy);
if (new_focus && new_focus == old_focus) {
wlr_seat_pointer_notify_enter(seat, new_focus, sx, sy);
wlr_seat_pointer_notify_motion(seat, time, sx, sy);
}
}
static void
cursor_motion_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_cursor *cursor =
wl_container_of(listener, cursor, cursor_motion);
struct kiwmi_server *server = cursor->server;
struct wlr_event_pointer_motion *event = data;
struct kiwmi_cursor_motion_event new_event = {
.oldx = cursor->cursor->x,
.oldy = cursor->cursor->y,
};
wlr_cursor_move(
cursor->cursor, event->device, event->delta_x, event->delta_y);
new_event.newx = cursor->cursor->x;
new_event.newy = cursor->cursor->y;
wl_signal_emit(&cursor->events.motion, &new_event);
process_cursor_motion(server, event->time_msec);
}
static void
cursor_motion_absolute_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_cursor *cursor =
wl_container_of(listener, cursor, cursor_motion_absolute);
struct kiwmi_server *server = cursor->server;
struct wlr_event_pointer_motion_absolute *event = data;
struct kiwmi_cursor_motion_event new_event = {
.oldx = cursor->cursor->x,
.oldy = cursor->cursor->y,
};
wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, event->y);
new_event.newx = cursor->cursor->x;
new_event.newy = cursor->cursor->y;
wl_signal_emit(&cursor->events.motion, &new_event);
process_cursor_motion(server, event->time_msec);
}
static void
cursor_button_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_cursor *cursor =
wl_container_of(listener, cursor, cursor_button);
struct kiwmi_server *server = cursor->server;
struct kiwmi_input *input = &server->input;
struct wlr_event_pointer_button *event = data;
struct kiwmi_cursor_button_event new_event = {
.wlr_event = event,
.handled = false,
};
if (event->state == WLR_BUTTON_PRESSED) {
wl_signal_emit(&cursor->events.button_down, &new_event);
} else {
wl_signal_emit(&cursor->events.button_up, &new_event);
}
if (!new_event.handled) {
wlr_seat_pointer_notify_button(
input->seat->seat, event->time_msec, event->button, event->state);
}
}
static void
cursor_axis_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_cursor *cursor =
wl_container_of(listener, cursor, cursor_axis);
struct kiwmi_server *server = cursor->server;
struct kiwmi_input *input = &server->input;
struct wlr_event_pointer_axis *event = data;
struct kiwmi_cursor_scroll_event new_event = {
.device_name = event->device->name,
.is_vertical = event->orientation == WLR_AXIS_ORIENTATION_VERTICAL,
.length = event->delta,
.handled = false,
};
wl_signal_emit(&cursor->events.scroll, &new_event);
if (!new_event.handled) {
wlr_seat_pointer_notify_axis(
input->seat->seat,
event->time_msec,
event->orientation,
event->delta,
event->delta_discrete,
event->source);
}
}
static void
cursor_frame_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_cursor *cursor =
wl_container_of(listener, cursor, cursor_frame);
struct kiwmi_server *server = cursor->server;
struct kiwmi_input *input = &server->input;
wlr_seat_pointer_notify_frame(input->seat->seat);
}
struct kiwmi_cursor *
cursor_create(
struct kiwmi_server *server,
struct wlr_output_layout *output_layout)
{
wlr_log(WLR_DEBUG, "Creating cursor");
struct kiwmi_cursor *cursor = malloc(sizeof(*cursor));
if (!cursor) {
wlr_log(WLR_ERROR, "Failed to allocate kiwmi_cursor");
return NULL;
}
cursor->server = server;
cursor->cursor_mode = KIWMI_CURSOR_PASSTHROUGH;
cursor->cursor = wlr_cursor_create();
if (!cursor->cursor) {
wlr_log(WLR_ERROR, "Failed to create cursor");
free(cursor);
return NULL;
}
wlr_cursor_attach_output_layout(cursor->cursor, output_layout);
cursor->xcursor_manager = wlr_xcursor_manager_create(NULL, 24);
cursor->cursor_motion.notify = cursor_motion_notify;
wl_signal_add(&cursor->cursor->events.motion, &cursor->cursor_motion);
cursor->cursor_motion_absolute.notify = cursor_motion_absolute_notify;
wl_signal_add(
&cursor->cursor->events.motion_absolute,
&cursor->cursor_motion_absolute);
cursor->cursor_button.notify = cursor_button_notify;
wl_signal_add(&cursor->cursor->events.button, &cursor->cursor_button);
cursor->cursor_axis.notify = cursor_axis_notify;
wl_signal_add(&cursor->cursor->events.axis, &cursor->cursor_axis);
cursor->cursor_frame.notify = cursor_frame_notify;
wl_signal_add(&cursor->cursor->events.frame, &cursor->cursor_frame);
wl_signal_init(&cursor->events.button_down);
wl_signal_init(&cursor->events.button_up);
wl_signal_init(&cursor->events.destroy);
wl_signal_init(&cursor->events.motion);
wl_signal_init(&cursor->events.scroll);
return cursor;
}
void
cursor_destroy(struct kiwmi_cursor *cursor)
{
wl_signal_emit(&cursor->events.destroy, cursor);
wlr_cursor_destroy(cursor->cursor);
wlr_xcursor_manager_destroy(cursor->xcursor_manager);
// The wlr_cursor is already destroyed, don't unregister listeners
free(cursor);
}
void
cursor_refresh_focus(
struct kiwmi_cursor *cursor,
struct wlr_surface **new_surface,
double *cursor_sx,
double *cursor_sy)
{
struct kiwmi_desktop *desktop = &cursor->server->desktop;
struct wlr_seat *seat = cursor->server->input.seat->seat;
double ox = cursor->cursor->x;
double oy = cursor->cursor->y;
struct wlr_output *wlr_output = wlr_output_layout_output_at(
desktop->output_layout, cursor->cursor->x, cursor->cursor->y);
wlr_output_layout_output_coords(
desktop->output_layout, wlr_output, &ox, &oy);
struct kiwmi_output *output = wlr_output->data;
struct wlr_surface *surface = NULL;
double sx;
double sy;
struct kiwmi_layer *layer = layer_at(
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
&surface,
ox,
oy,
&sx,
&sy);
struct kiwmi_view *view;
if (!layer) {
layer = layer_at(
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
&surface,
ox,
oy,
&sx,
&sy);
}
if (!layer) {
view = view_at(
desktop, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
}
if (!layer) {
layer = layer_at(
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
&surface,
ox,
oy,
&sx,
&sy);
}
if (!layer) {
layer = layer_at(
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
&surface,
ox,
oy,
&sx,
&sy);
}
if (!layer && !view) {
wlr_xcursor_manager_set_cursor_image(
cursor->xcursor_manager, "left_ptr", cursor->cursor);
}
if (surface && surface != seat->pointer_state.focused_surface) {
wlr_seat_pointer_notify_enter(seat, surface, sx, sy);
} else if (!surface) {
wlr_seat_pointer_clear_focus(seat);
}
if (new_surface) {
*new_surface = surface;
}
if (cursor_sx) {
*cursor_sx = surface ? sx : 0;
}
if (cursor_sy) {
*cursor_sy = surface ? sy : 0;
}
}

110
kiwmi/kiwmi/input/input.c Normal file
View File

@ -0,0 +1,110 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "input/input.h"
#include <stdlib.h>
#include <wayland-server.h>
#include <wlr/backend.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/util/log.h>
#include "desktop/desktop.h"
#include "input/cursor.h"
#include "input/keyboard.h"
#include "input/seat.h"
#include "server.h"
static void
new_pointer(struct kiwmi_input *input, struct wlr_input_device *device)
{
wlr_cursor_attach_input_device(input->cursor->cursor, device);
}
static void
new_keyboard(struct kiwmi_input *input, struct wlr_input_device *device)
{
struct kiwmi_server *server = wl_container_of(input, server, input);
struct kiwmi_keyboard *keyboard = keyboard_create(server, device);
if (!keyboard) {
return;
}
wl_list_insert(&input->keyboards, &keyboard->link);
}
static void
new_input_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_input *input = wl_container_of(listener, input, new_input);
struct wlr_input_device *device = data;
wlr_log(WLR_DEBUG, "New input %p: %s", device, device->name);
switch (device->type) {
case WLR_INPUT_DEVICE_POINTER:
new_pointer(input, device);
break;
case WLR_INPUT_DEVICE_KEYBOARD:
new_keyboard(input, device);
break;
default:
// NOT HANDLED
break;
}
uint32_t caps = WL_SEAT_CAPABILITY_POINTER;
if (!wl_list_empty(&input->keyboards)) {
caps |= WL_SEAT_CAPABILITY_KEYBOARD;
}
wlr_seat_set_capabilities(input->seat->seat, caps);
}
bool
input_init(struct kiwmi_input *input)
{
struct kiwmi_server *server = wl_container_of(input, server, input);
input->seat = seat_create(input);
if (!input->seat) {
return false;
}
input->cursor = cursor_create(server, server->desktop.output_layout);
if (!input->cursor) {
wlr_log(WLR_ERROR, "Failed to create cursor");
return false;
}
wl_list_init(&input->keyboards);
input->new_input.notify = new_input_notify;
wl_signal_add(&server->backend->events.new_input, &input->new_input);
wl_signal_init(&input->events.keyboard_new);
return true;
}
void
input_fini(struct kiwmi_input *input)
{
struct kiwmi_keyboard *keyboard;
struct kiwmi_keyboard *tmp;
wl_list_for_each_safe (keyboard, tmp, &input->keyboards, link) {
keyboard_destroy(keyboard);
}
seat_destroy(input->seat);
cursor_destroy(input->cursor);
}

View File

@ -0,0 +1,178 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "input/keyboard.h"
#include <stdbool.h>
#include <stdlib.h>
#include <wayland-server.h>
#include <wlr/backend.h>
#include <wlr/backend/multi.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/util/log.h>
#include <xkbcommon/xkbcommon.h>
#include "input/seat.h"
#include "server.h"
static bool
switch_vt(const xkb_keysym_t *syms, int nsyms, struct wlr_backend *backend)
{
for (int i = 0; i < nsyms; ++i) {
const xkb_keysym_t sym = syms[i];
if (sym >= XKB_KEY_XF86Switch_VT_1 && sym <= XKB_KEY_XF86Switch_VT_12) {
if (wlr_backend_is_multi(backend)) {
struct wlr_session *session = wlr_backend_get_session(backend);
if (session) {
unsigned vt = sym - XKB_KEY_XF86Switch_VT_1 + 1;
wlr_session_change_vt(session, vt);
}
}
return true;
}
}
return false;
}
static void
keyboard_modifiers_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_keyboard *keyboard =
wl_container_of(listener, keyboard, modifiers);
wlr_seat_set_keyboard(keyboard->server->input.seat->seat, keyboard->device);
wlr_seat_keyboard_notify_modifiers(
keyboard->server->input.seat->seat,
&keyboard->device->keyboard->modifiers);
}
static void
keyboard_key_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_keyboard *keyboard = wl_container_of(listener, keyboard, key);
struct kiwmi_server *server = keyboard->server;
struct wlr_event_keyboard_key *event = data;
struct wlr_input_device *device = keyboard->device;
uint32_t keycode = event->keycode + 8;
const xkb_keysym_t *raw_syms;
xkb_layout_index_t layout_index =
xkb_state_key_get_layout(device->keyboard->xkb_state, keycode);
int raw_syms_len = xkb_keymap_key_get_syms_by_level(
device->keyboard->keymap, keycode, layout_index, 0, &raw_syms);
const xkb_keysym_t *translated_syms;
int translated_syms_len = xkb_state_key_get_syms(
keyboard->device->keyboard->xkb_state, keycode, &translated_syms);
bool handled = false;
if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
handled =
switch_vt(translated_syms, translated_syms_len, server->backend);
}
if (!handled) {
struct kiwmi_keyboard_key_event data = {
.raw_syms = raw_syms,
.translated_syms = translated_syms,
.raw_syms_len = raw_syms_len,
.translated_syms_len = translated_syms_len,
.keycode = keycode,
.keyboard = keyboard,
.handled = false,
};
if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
wl_signal_emit(&keyboard->events.key_down, &data);
} else {
wl_signal_emit(&keyboard->events.key_up, &data);
}
handled = data.handled;
}
if (!handled) {
wlr_seat_set_keyboard(server->input.seat->seat, keyboard->device);
wlr_seat_keyboard_notify_key(
server->input.seat->seat,
event->time_msec,
event->keycode,
event->state);
}
}
static void
keyboard_destroy_notify(struct wl_listener *listener, void *UNUSED(data))
{
struct kiwmi_keyboard *keyboard =
wl_container_of(listener, keyboard, device_destroy);
keyboard_destroy(keyboard);
}
struct kiwmi_keyboard *
keyboard_create(struct kiwmi_server *server, struct wlr_input_device *device)
{
wlr_log(WLR_DEBUG, "Creating keyboard");
struct kiwmi_keyboard *keyboard = malloc(sizeof(*keyboard));
if (!keyboard) {
return NULL;
}
keyboard->server = server;
keyboard->device = device;
keyboard->modifiers.notify = keyboard_modifiers_notify;
wl_signal_add(&device->keyboard->events.modifiers, &keyboard->modifiers);
keyboard->key.notify = keyboard_key_notify;
wl_signal_add(&device->keyboard->events.key, &keyboard->key);
keyboard->device_destroy.notify = keyboard_destroy_notify;
wl_signal_add(&device->events.destroy, &keyboard->device_destroy);
struct xkb_rule_names rules = {0};
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
struct xkb_keymap *keymap =
xkb_map_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS);
wlr_keyboard_set_keymap(device->keyboard, keymap);
xkb_keymap_unref(keymap);
xkb_context_unref(context);
wlr_keyboard_set_repeat_info(device->keyboard, 25, 600);
wlr_seat_set_keyboard(server->input.seat->seat, device);
wl_signal_init(&keyboard->events.key_down);
wl_signal_init(&keyboard->events.key_up);
wl_signal_init(&keyboard->events.destroy);
wl_signal_emit(&server->input.events.keyboard_new, keyboard);
return keyboard;
}
void
keyboard_destroy(struct kiwmi_keyboard *keyboard)
{
wl_list_remove(&keyboard->modifiers.link);
wl_list_remove(&keyboard->key.link);
wl_list_remove(&keyboard->device_destroy.link);
wl_signal_emit(&keyboard->events.destroy, keyboard);
wl_list_remove(&keyboard->link);
wl_list_remove(&keyboard->events.destroy.listener_list);
free(keyboard);
}

188
kiwmi/kiwmi/input/seat.c Normal file
View File

@ -0,0 +1,188 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "input/seat.h"
#include <stdlib.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_layer_shell_v1.h>
#include <wlr/types/wlr_primary_selection.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/util/log.h>
#include "desktop/layer_shell.h"
#include "desktop/view.h"
#include "input/cursor.h"
#include "server.h"
void
seat_focus_surface(struct kiwmi_seat *seat, struct wlr_surface *wlr_surface)
{
if (seat->focused_layer) {
return;
}
struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->seat);
if (!keyboard) {
wlr_seat_keyboard_enter(seat->seat, wlr_surface, NULL, 0, NULL);
}
wlr_seat_keyboard_enter(
seat->seat,
wlr_surface,
keyboard->keycodes,
keyboard->num_keycodes,
&keyboard->modifiers);
}
void
seat_focus_layer(struct kiwmi_seat *seat, struct kiwmi_layer *layer)
{
if (!layer) {
seat->focused_layer = NULL;
if (seat->focused_view) {
seat_focus_surface(seat, seat->focused_view->wlr_surface);
}
return;
}
if (seat->focused_layer == layer) {
return;
}
seat->focused_layer = NULL;
seat_focus_surface(seat, layer->layer_surface->surface);
seat->focused_layer = layer;
}
void
seat_focus_view(struct kiwmi_seat *seat, struct kiwmi_view *view)
{
if (!view) {
seat_focus_surface(seat, NULL);
seat->focused_view = NULL;
return;
}
struct kiwmi_desktop *desktop = view->desktop;
if (seat->focused_view) {
view_set_activated(seat->focused_view, false);
}
// move view to front
wl_list_remove(&view->link);
wl_list_insert(&desktop->views, &view->link);
cursor_refresh_focus(seat->input->cursor, NULL, NULL, NULL);
seat->focused_view = view;
view_set_activated(view, true);
seat_focus_surface(seat, view->wlr_surface);
}
static void
request_set_cursor_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_seat *seat =
wl_container_of(listener, seat, request_set_cursor);
struct kiwmi_cursor *cursor = seat->input->cursor;
struct wlr_seat_pointer_request_set_cursor_event *event = data;
struct wlr_surface *focused_surface =
event->seat_client->seat->pointer_state.focused_surface;
struct wl_client *focused_client = NULL;
if (focused_surface && focused_surface->resource) {
focused_client = wl_resource_get_client(focused_surface->resource);
}
if (event->seat_client->client != focused_client) {
wlr_log(
WLR_DEBUG, "Ignoring request to set cursor on unfocused client");
return;
}
struct kiwmi_input *input = seat->input;
struct kiwmi_server *server = wl_container_of(input, server, input);
struct kiwmi_output *output;
wl_list_for_each (output, &server->desktop.outputs, link) {
output_damage(output);
}
wlr_cursor_set_surface(
cursor->cursor, event->surface, event->hotspot_x, event->hotspot_y);
}
static void
request_set_selection_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_seat *seat =
wl_container_of(listener, seat, request_set_selection);
struct wlr_seat_request_set_selection_event *event = data;
wlr_seat_set_selection(seat->seat, event->source, event->serial);
}
static void
request_set_primary_selection_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_seat *seat =
wl_container_of(listener, seat, request_set_primary_selection);
struct wlr_seat_request_set_primary_selection_event *event = data;
wlr_seat_set_primary_selection(seat->seat, event->source, event->serial);
}
struct kiwmi_seat *
seat_create(struct kiwmi_input *input)
{
struct kiwmi_server *server = wl_container_of(input, server, input);
struct kiwmi_seat *seat = malloc(sizeof(*seat));
if (!seat) {
wlr_log(WLR_ERROR, "Failed to allocate kiwmi_seat");
return NULL;
}
seat->input = input;
seat->seat = wlr_seat_create(server->wl_display, "seat-0");
seat->focused_view = NULL;
seat->focused_layer = NULL;
seat->request_set_cursor.notify = request_set_cursor_notify;
wl_signal_add(
&seat->seat->events.request_set_cursor, &seat->request_set_cursor);
wl_signal_add(
&seat->seat->events.request_set_selection,
&seat->request_set_selection);
seat->request_set_selection.notify = request_set_selection_notify;
wl_signal_add(
&seat->seat->events.request_set_primary_selection,
&seat->request_set_primary_selection);
seat->request_set_primary_selection.notify =
request_set_primary_selection_notify;
return seat;
}
void
seat_destroy(struct kiwmi_seat *seat)
{
wl_list_remove(&seat->request_set_cursor.link);
wl_list_remove(&seat->request_set_selection.link);
wl_list_remove(&seat->request_set_primary_selection.link);
free(seat);
}

117
kiwmi/kiwmi/luak/ipc.c Normal file
View File

@ -0,0 +1,117 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "luak/ipc.h"
#include <lauxlib.h>
#include <wlr/util/log.h>
#include "kiwmi-ipc-protocol.h"
#include "luak/luak.h"
static void
ipc_eval(
struct wl_client *client,
struct wl_resource *resource,
uint32_t id,
const char *message)
{
struct kiwmi_server *server = wl_resource_get_user_data(resource);
struct wl_resource *command_resource =
wl_resource_create(client, &kiwmi_command_interface, 1, id);
lua_State *L = server->lua->L;
int top = lua_gettop(L);
lua_pushboolean(L, true);
lua_setglobal(L, "FROM_KIWMIC");
if (luaL_dostring(L, message)) {
const char *error = lua_tostring(L, -1);
wlr_log(WLR_ERROR, "Error running IPC command: %s", error);
kiwmi_command_send_done(
command_resource, KIWMI_COMMAND_ERROR_FAILURE, error);
lua_pop(L, 1);
lua_pushboolean(L, false);
lua_setglobal(L, "FROM_KIWMIC");
return;
}
lua_pushboolean(L, false);
lua_setglobal(L, "FROM_KIWMIC");
int results = top - lua_gettop(L);
if (results == 0) {
kiwmi_command_send_done(
command_resource, KIWMI_COMMAND_ERROR_SUCCESS, "");
} else {
lua_getglobal(L, "tostring");
lua_insert(L, -2);
if (lua_pcall(L, 1, 1, 0)) {
const char *error = lua_tostring(L, -1);
wlr_log(WLR_ERROR, "Error running IPC command: %s", error);
kiwmi_command_send_done(
command_resource, KIWMI_COMMAND_ERROR_FAILURE, error);
lua_pop(L, 1);
return;
}
kiwmi_command_send_done(
command_resource, KIWMI_COMMAND_ERROR_SUCCESS, lua_tostring(L, -1));
}
lua_pop(L, results);
}
static const struct kiwmi_ipc_interface kiwmi_ipc_implementation = {
.eval = ipc_eval,
};
static void
kiwmi_server_resource_destroy(struct wl_resource *UNUSED(resource))
{
// EMPTY
}
static void
ipc_server_bind(
struct wl_client *client,
void *data,
uint32_t version,
uint32_t id)
{
struct kiwmi_server *server = data;
struct wl_resource *resource =
wl_resource_create(client, &kiwmi_ipc_interface, version, id);
if (!resource) {
wl_client_post_no_memory(client);
return;
}
wl_resource_set_implementation(
resource,
&kiwmi_ipc_implementation,
server,
kiwmi_server_resource_destroy);
}
bool
luaK_ipc_init(struct kiwmi_server *server, struct kiwmi_lua *lua)
{
lua->global = wl_global_create(
server->wl_display, &kiwmi_ipc_interface, 1, server, ipc_server_bind);
if (!lua->global) {
wlr_log(WLR_ERROR, "Failed to create IPC global");
return false;
}
return true;
}

View File

@ -0,0 +1,343 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "luak/kiwmi_cursor.h"
#include <linux/input-event-codes.h>
#include <lauxlib.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_pointer.h>
#include <wlr/util/log.h>
#include "desktop/view.h"
#include "input/cursor.h"
#include "luak/kiwmi_lua_callback.h"
#include "luak/kiwmi_output.h"
#include "luak/kiwmi_view.h"
#include "luak/lua_compat.h"
#include "luak/luak.h"
static int
l_kiwmi_cursor_output_at_pos(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_cursor");
struct kiwmi_cursor *cursor = obj->object;
struct kiwmi_server *server = cursor->server;
struct wlr_output *wlr_output = wlr_output_layout_output_at(
server->desktop.output_layout, cursor->cursor->x, cursor->cursor->y);
if (wlr_output) {
lua_pushcfunction(L, luaK_kiwmi_output_new);
lua_pushlightuserdata(L, obj->lua);
lua_pushlightuserdata(L, wlr_output->data);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
} else {
lua_pushnil(L);
}
return 1;
}
static int
l_kiwmi_cursor_pos(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_cursor");
struct kiwmi_cursor *cursor = obj->object;
lua_pushnumber(L, cursor->cursor->x);
lua_pushnumber(L, cursor->cursor->y);
return 2;
}
static int
l_kiwmi_cursor_view_at_pos(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_cursor");
struct kiwmi_lua *lua = obj->lua;
struct kiwmi_cursor *cursor = obj->object;
struct kiwmi_server *server = cursor->server;
struct wlr_surface *surface;
double sx;
double sy;
struct kiwmi_view *view = view_at(
&server->desktop,
cursor->cursor->x,
cursor->cursor->y,
&surface,
&sx,
&sy);
if (view) {
lua_pushcfunction(L, luaK_kiwmi_view_new);
lua_pushlightuserdata(L, lua);
lua_pushlightuserdata(L, view);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
} else {
lua_pushnil(L);
}
return 1;
}
static const luaL_Reg kiwmi_cursor_methods[] = {
{"on", luaK_callback_register_dispatch},
{"output_at_pos", l_kiwmi_cursor_output_at_pos},
{"pos", l_kiwmi_cursor_pos},
{"view_at_pos", l_kiwmi_cursor_view_at_pos},
{NULL, NULL},
};
static void
kiwmi_cursor_on_button_down_or_up_notify(
struct wl_listener *listener,
void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_cursor_button_event *event = data;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_pushinteger(L, event->wlr_event->button - BTN_LEFT + 1);
if (lua_pcall(L, 1, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
event->handled |= lua_toboolean(L, -1);
lua_pop(L, 1);
}
static void
kiwmi_cursor_on_motion_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_cursor_motion_event *event = data;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_newtable(L);
lua_pushnumber(L, event->oldx);
lua_setfield(L, -2, "oldx");
lua_pushnumber(L, event->oldy);
lua_setfield(L, -2, "oldy");
lua_pushnumber(L, event->newx);
lua_setfield(L, -2, "newx");
lua_pushnumber(L, event->newy);
lua_setfield(L, -2, "newy");
if (lua_pcall(L, 1, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
static void
kiwmi_cursor_on_scroll_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_cursor_scroll_event *event = data;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_newtable(L);
lua_pushstring(L, event->device_name);
lua_setfield(L, -2, "device");
lua_pushnumber(L, event->is_vertical);
lua_setfield(L, -2, "vertical");
lua_pushnumber(L, event->length);
lua_setfield(L, -2, "length");
if (lua_pcall(L, 1, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
event->handled |= lua_toboolean(L, -1);
lua_pop(L, 1);
}
static int
l_kiwmi_cursor_on_button_down(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_cursor");
luaL_checktype(L, 2, LUA_TFUNCTION);
struct kiwmi_cursor *cursor = obj->object;
struct kiwmi_server *server = cursor->server;
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_cursor_on_button_down_or_up_notify);
lua_pushlightuserdata(L, &cursor->events.button_down);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_cursor_on_button_up(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_cursor");
luaL_checktype(L, 2, LUA_TFUNCTION);
struct kiwmi_cursor *cursor = obj->object;
struct kiwmi_server *server = cursor->server;
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_cursor_on_button_down_or_up_notify);
lua_pushlightuserdata(L, &cursor->events.button_up);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_cursor_on_motion(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_cursor");
luaL_checktype(L, 2, LUA_TFUNCTION);
struct kiwmi_cursor *cursor = obj->object;
struct kiwmi_server *server = cursor->server;
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_cursor_on_motion_notify);
lua_pushlightuserdata(L, &cursor->events.motion);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_cursor_on_scroll(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_cursor");
luaL_checktype(L, 2, LUA_TFUNCTION);
struct kiwmi_cursor *cursor = obj->object;
struct kiwmi_server *server = cursor->server;
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_cursor_on_scroll_notify);
lua_pushlightuserdata(L, &cursor->events.scroll);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static const luaL_Reg kiwmi_cursor_events[] = {
{"button_down", l_kiwmi_cursor_on_button_down},
{"button_up", l_kiwmi_cursor_on_button_up},
{"motion", l_kiwmi_cursor_on_motion},
{"scroll", l_kiwmi_cursor_on_scroll},
{NULL, NULL},
};
int
luaK_kiwmi_cursor_new(lua_State *L)
{
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA); // kiwmi_lua
luaL_checktype(L, 2, LUA_TLIGHTUSERDATA); // kiwmi_cursor
struct kiwmi_lua *lua = lua_touserdata(L, 1);
struct kiwmi_cursor *cursor = lua_touserdata(L, 2);
struct kiwmi_object *obj =
luaK_get_kiwmi_object(lua, cursor, &cursor->events.destroy);
struct kiwmi_object **cursor_ud = lua_newuserdata(L, sizeof(*cursor_ud));
luaL_getmetatable(L, "kiwmi_cursor");
lua_setmetatable(L, -2);
*cursor_ud = obj;
return 1;
}
int
luaK_kiwmi_cursor_register(lua_State *L)
{
luaL_newmetatable(L, "kiwmi_cursor");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaC_setfuncs(L, kiwmi_cursor_methods, 0);
luaC_newlib(L, kiwmi_cursor_events);
lua_setfield(L, -2, "__events");
lua_pushcfunction(L, luaK_usertype_ref_equal);
lua_setfield(L, -2, "__eq");
lua_pushcfunction(L, luaK_kiwmi_object_gc);
lua_setfield(L, -2, "__gc");
return 0;
}

View File

@ -0,0 +1,369 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "luak/kiwmi_keyboard.h"
#include <stdint.h>
#include <lauxlib.h>
#include <wayland-server.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/util/log.h>
#include <xkbcommon/xkbcommon.h>
#include "input/keyboard.h"
#include "luak/kiwmi_lua_callback.h"
#include "luak/lua_compat.h"
static int
l_kiwmi_keyboard_keymap(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_keyboard");
luaL_checktype(L, 2, LUA_TTABLE);
if (!obj->valid) {
return luaL_error(L, "kiwmi_keyboard no longer valid");
}
struct kiwmi_keyboard *keyboard = obj->object;
struct xkb_rule_names settings = {0};
lua_getfield(L, 2, "rules");
if (lua_isstring(L, -1)) {
settings.rules = luaL_checkstring(L, -1);
}
lua_getfield(L, 2, "model");
if (lua_isstring(L, -1)) {
settings.model = luaL_checkstring(L, -1);
}
lua_getfield(L, 2, "layout");
if (lua_isstring(L, -1)) {
settings.layout = luaL_checkstring(L, -1);
}
lua_getfield(L, 2, "variant");
if (lua_isstring(L, -1)) {
settings.variant = luaL_checkstring(L, -1);
}
lua_getfield(L, 2, "options");
if (lua_isstring(L, -1)) {
settings.options = luaL_checkstring(L, -1);
}
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
struct xkb_keymap *keymap = xkb_keymap_new_from_names(
context, &settings, XKB_KEYMAP_COMPILE_NO_FLAGS);
wlr_keyboard_set_keymap(keyboard->device->keyboard, keymap);
xkb_keymap_unref(keymap);
xkb_context_unref(context);
return 0;
}
static int
l_kiwmi_keyboard_modifiers(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_keyboard");
if (!obj->valid) {
return luaL_error(L, "kiwmi_keyboard no longer valid");
}
struct kiwmi_keyboard *keyboard = obj->object;
uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard->device->keyboard);
lua_newtable(L);
lua_pushboolean(L, modifiers & WLR_MODIFIER_SHIFT);
lua_setfield(L, -2, "shift");
lua_pushboolean(L, modifiers & WLR_MODIFIER_CAPS);
lua_setfield(L, -2, "caps");
lua_pushboolean(L, modifiers & WLR_MODIFIER_CTRL);
lua_setfield(L, -2, "ctrl");
lua_pushboolean(L, modifiers & WLR_MODIFIER_ALT);
lua_setfield(L, -2, "alt");
lua_pushboolean(L, modifiers & WLR_MODIFIER_MOD2);
lua_setfield(L, -2, "mod2");
lua_pushboolean(L, modifiers & WLR_MODIFIER_MOD3);
lua_setfield(L, -2, "mod3");
lua_pushboolean(L, modifiers & WLR_MODIFIER_LOGO);
lua_setfield(L, -2, "super");
lua_pushboolean(L, modifiers & WLR_MODIFIER_MOD5);
lua_setfield(L, -2, "mod5");
return 1;
}
static const luaL_Reg kiwmi_keyboard_methods[] = {
{"keymap", l_kiwmi_keyboard_keymap},
{"modifiers", l_kiwmi_keyboard_modifiers},
{"on", luaK_callback_register_dispatch},
{NULL, NULL},
};
static void
kiwmi_keyboard_on_destroy_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_keyboard *keyboard = data;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_pushcfunction(L, luaK_kiwmi_keyboard_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, keyboard);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
if (lua_pcall(L, 1, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
}
static bool
send_key_event(
struct kiwmi_lua_callback *lc,
xkb_keysym_t sym,
uint32_t keycode,
struct kiwmi_keyboard *keyboard,
bool raw)
{
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
static char keysym_name[64];
size_t namelen = xkb_keysym_get_name(sym, keysym_name, sizeof(keysym_name));
namelen = namelen > sizeof(keysym_name) ? sizeof(keysym_name) : namelen;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_newtable(L);
lua_pushlstring(L, keysym_name, namelen);
lua_setfield(L, -2, "key");
lua_pushnumber(L, keycode);
lua_setfield(L, -2, "keycode");
lua_pushboolean(L, raw);
lua_setfield(L, -2, "raw");
lua_pushcfunction(L, luaK_kiwmi_keyboard_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, keyboard);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return false;
}
lua_setfield(L, -2, "keyboard");
if (lua_pcall(L, 1, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return false;
}
bool handled = lua_toboolean(L, -1);
lua_pop(L, 1);
return handled;
}
static void
kiwmi_keyboard_on_key_down_or_up_notify(
struct wl_listener *listener,
void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_keyboard_key_event *event = data;
struct kiwmi_keyboard *keyboard = event->keyboard;
const xkb_keysym_t *raw_syms = event->raw_syms;
int raw_syms_len = event->raw_syms_len;
const xkb_keysym_t *translated_syms = event->translated_syms;
int translated_syms_len = event->translated_syms_len;
uint32_t keycode = event->keycode;
bool handled = false;
for (int i = 0; i < translated_syms_len; ++i) {
xkb_keysym_t sym = translated_syms[i];
handled |= send_key_event(lc, sym, keycode, keyboard, false);
}
if (!handled) {
for (int i = 0; i < raw_syms_len; ++i) {
xkb_keysym_t sym = raw_syms[i];
handled |= send_key_event(lc, sym, keycode, keyboard, true);
}
}
event->handled = handled;
}
static int
l_kiwmi_keyboard_on_destroy(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_keyboard");
luaL_checktype(L, 2, LUA_TFUNCTION);
if (!obj->valid) {
return luaL_error(L, "kiwmi_keyboard no longer valid");
}
struct kiwmi_keyboard *keyboard = obj->object;
struct kiwmi_server *server = keyboard->server;
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_keyboard_on_destroy_notify);
lua_pushlightuserdata(L, &obj->events.destroy);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_keyboard_on_key_down(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_keyboard");
luaL_checktype(L, 2, LUA_TFUNCTION);
if (!obj->valid) {
return luaL_error(L, "kiwmi_keyboard no longer valid");
}
struct kiwmi_keyboard *keyboard = obj->object;
struct kiwmi_server *server = keyboard->server;
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_keyboard_on_key_down_or_up_notify);
lua_pushlightuserdata(L, &keyboard->events.key_down);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_keyboard_on_key_up(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_keyboard");
luaL_checktype(L, 2, LUA_TFUNCTION);
if (!obj->valid) {
return luaL_error(L, "kiwmi_keyboard no longer valid");
}
struct kiwmi_keyboard *keyboard = obj->object;
struct kiwmi_server *server = keyboard->server;
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_keyboard_on_key_down_or_up_notify);
lua_pushlightuserdata(L, &keyboard->events.key_up);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static const luaL_Reg kiwmi_keyboard_events[] = {
{"destroy", l_kiwmi_keyboard_on_destroy},
{"key_down", l_kiwmi_keyboard_on_key_down},
{"key_up", l_kiwmi_keyboard_on_key_up},
{NULL, NULL},
};
int
luaK_kiwmi_keyboard_new(lua_State *L)
{
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA); // kiwmi_lua
luaL_checktype(L, 2, LUA_TLIGHTUSERDATA); // kiwmi_keyboard
struct kiwmi_lua *lua = lua_touserdata(L, 1);
struct kiwmi_keyboard *keyboard = lua_touserdata(L, 2);
struct kiwmi_object *obj =
luaK_get_kiwmi_object(lua, keyboard, &keyboard->events.destroy);
struct kiwmi_object **keyboard_ud =
lua_newuserdata(L, sizeof(*keyboard_ud));
luaL_getmetatable(L, "kiwmi_keyboard");
lua_setmetatable(L, -2);
*keyboard_ud = obj;
return 1;
}
int
luaK_kiwmi_keyboard_register(lua_State *L)
{
luaL_newmetatable(L, "kiwmi_keyboard");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaC_setfuncs(L, kiwmi_keyboard_methods, 0);
luaC_newlib(L, kiwmi_keyboard_events);
lua_setfield(L, -2, "__events");
lua_pushcfunction(L, luaK_usertype_ref_equal);
lua_setfield(L, -2, "__eq");
lua_pushcfunction(L, luaK_kiwmi_object_gc);
lua_setfield(L, -2, "__gc");
return 0;
}

View File

@ -0,0 +1,44 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "luak/kiwmi_lua_callback.h"
#include <stdlib.h>
#include <lauxlib.h>
#include <wayland-server.h>
int
luaK_kiwmi_lua_callback_new(lua_State *L)
{
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA); // server
luaL_checktype(L, 2, LUA_TFUNCTION); // callback
luaL_checktype(L, 3, LUA_TLIGHTUSERDATA); // event_handler
luaL_checktype(L, 4, LUA_TLIGHTUSERDATA); // signal
luaL_checktype(L, 5, LUA_TLIGHTUSERDATA); // object
struct kiwmi_lua_callback *lc = malloc(sizeof(*lc));
if (!lc) {
return luaL_error(L, "failed to allocate kiwmi_lua_callback");
}
struct kiwmi_server *server = lua_touserdata(L, 1);
lc->server = server;
lua_pushvalue(L, 2);
lc->callback_ref = luaL_ref(L, LUA_REGISTRYINDEX);
lc->listener.notify = lua_touserdata(L, 3);
wl_signal_add(lua_touserdata(L, 4), &lc->listener);
struct kiwmi_object *object = lua_touserdata(L, 5);
wl_list_insert(&object->callbacks, &lc->link);
return 0;
}

View File

@ -0,0 +1,424 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "luak/kiwmi_output.h"
#include <lauxlib.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include "desktop/output.h"
#include "luak/kiwmi_lua_callback.h"
#include "luak/lua_compat.h"
#include "server.h"
static int
l_kiwmi_output_auto(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_output");
if (!obj->valid) {
return luaL_error(L, "kiwmi_output no longer valid");
}
struct kiwmi_output *output = obj->object;
struct wlr_output_layout *output_layout = output->desktop->output_layout;
wlr_output_layout_add_auto(output_layout, output->wlr_output);
return 0;
}
static int
l_kiwmi_output_move(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_output");
luaL_checktype(L, 2, LUA_TNUMBER); // x
luaL_checktype(L, 3, LUA_TNUMBER); // y
if (!obj->valid) {
return luaL_error(L, "kiwmi_output no longer valid");
}
struct kiwmi_output *output = obj->object;
struct wlr_output_layout *output_layout = output->desktop->output_layout;
int lx = lua_tonumber(L, 2);
int ly = lua_tonumber(L, 3);
wlr_output_layout_move(output_layout, output->wlr_output, lx, ly);
return 0;
}
static int
l_kiwmi_output_name(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_output");
if (!obj->valid) {
return luaL_error(L, "kiwmi_output no longer valid");
}
struct kiwmi_output *output = obj->object;
lua_pushstring(L, output->wlr_output->name);
return 1;
}
static int
l_kiwmi_output_pos(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_output");
if (!obj->valid) {
return luaL_error(L, "kiwmi_output no longer valid");
}
struct kiwmi_output *output = obj->object;
struct wlr_output_layout *output_layout = output->desktop->output_layout;
struct wlr_box *box =
wlr_output_layout_get_box(output_layout, output->wlr_output);
lua_pushinteger(L, box->x);
lua_pushinteger(L, box->y);
return 2;
}
static int
l_kiwmi_output_redraw(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_output");
if (!obj->valid) {
return luaL_error(L, "kiwmi_output no longer valid");
}
struct kiwmi_output *output = obj->object;
output_damage(output);
return 0;
}
static int
l_kiwmi_output_size(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_output");
if (!obj->valid) {
return luaL_error(L, "kiwmi_output no longer valid");
}
struct kiwmi_output *output = obj->object;
lua_pushinteger(L, output->wlr_output->width);
lua_pushinteger(L, output->wlr_output->height);
return 2;
}
static int
l_kiwmi_output_usable_area(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_output");
if (!obj->valid) {
return luaL_error(L, "kiwmi_output no longer valid");
}
struct kiwmi_output *output = obj->object;
lua_newtable(L);
lua_pushinteger(L, output->usable_area.x);
lua_setfield(L, -2, "x");
lua_pushinteger(L, output->usable_area.y);
lua_setfield(L, -2, "y");
lua_pushinteger(L, output->usable_area.width);
lua_setfield(L, -2, "width");
lua_pushinteger(L, output->usable_area.height);
lua_setfield(L, -2, "height");
return 1;
}
static const luaL_Reg kiwmi_output_methods[] = {
{"auto", l_kiwmi_output_auto},
{"move", l_kiwmi_output_move},
{"name", l_kiwmi_output_name},
{"on", luaK_callback_register_dispatch},
{"pos", l_kiwmi_output_pos},
{"redraw", l_kiwmi_output_redraw},
{"size", l_kiwmi_output_size},
{"usable_area", l_kiwmi_output_usable_area},
{NULL, NULL},
};
static void
kiwmi_output_on_destroy_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_output *output = data;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_pushcfunction(L, luaK_kiwmi_output_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, output);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
if (lua_pcall(L, 1, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
static void
kiwmi_output_on_resize_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_output *output = data;
int width;
int height;
wlr_output_transformed_resolution(output->wlr_output, &width, &height);
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_newtable(L);
lua_pushcfunction(L, luaK_kiwmi_output_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, output);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
lua_setfield(L, -2, "output");
lua_pushinteger(L, width);
lua_setfield(L, -2, "width");
lua_pushinteger(L, height);
lua_setfield(L, -2, "height");
if (lua_pcall(L, 1, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
static void
kiwmi_output_on_usable_area_change_notify(
struct wl_listener *listener,
void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_output *output = data;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_newtable(L);
lua_pushcfunction(L, luaK_kiwmi_output_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, output);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
lua_setfield(L, -2, "output");
lua_pushinteger(L, output->usable_area.x);
lua_setfield(L, -2, "x");
lua_pushinteger(L, output->usable_area.y);
lua_setfield(L, -2, "y");
lua_pushinteger(L, output->usable_area.width);
lua_setfield(L, -2, "width");
lua_pushinteger(L, output->usable_area.height);
lua_setfield(L, -2, "height");
if (lua_pcall(L, 1, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
static int
l_kiwmi_output_on_destroy(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_output");
luaL_checktype(L, 2, LUA_TFUNCTION);
if (!obj->valid) {
return luaL_error(L, "kiwmi_output no longer valid");
}
struct kiwmi_output *output = obj->object;
struct kiwmi_desktop *desktop = output->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_output_on_destroy_notify);
lua_pushlightuserdata(L, &obj->events.destroy);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_output_on_resize(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_output");
luaL_checktype(L, 2, LUA_TFUNCTION);
if (!obj->valid) {
return luaL_error(L, "kiwmi_output no longer valid");
}
struct kiwmi_output *output = obj->object;
struct kiwmi_desktop *desktop = output->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_output_on_resize_notify);
lua_pushlightuserdata(L, &output->events.resize);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_output_on_usable_area_change(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_output");
luaL_checktype(L, 2, LUA_TFUNCTION);
if (!obj->valid) {
return luaL_error(L, "kiwmi_output no longer valid");
}
struct kiwmi_output *output = obj->object;
struct kiwmi_desktop *desktop = output->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_output_on_usable_area_change_notify);
lua_pushlightuserdata(L, &output->events.usable_area_change);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static const luaL_Reg kiwmi_output_events[] = {
{"destroy", l_kiwmi_output_on_destroy},
{"resize", l_kiwmi_output_on_resize},
{"usable_area_change", l_kiwmi_output_on_usable_area_change},
{NULL, NULL},
};
int
luaK_kiwmi_output_new(lua_State *L)
{
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA); // kiwmi_lua
luaL_checktype(L, 2, LUA_TLIGHTUSERDATA); // kiwmi_output
struct kiwmi_lua *lua = lua_touserdata(L, 1);
struct kiwmi_output *output = lua_touserdata(L, 2);
struct kiwmi_object *obj =
luaK_get_kiwmi_object(lua, output, &output->events.destroy);
struct kiwmi_object **output_ud = lua_newuserdata(L, sizeof(*output_ud));
luaL_getmetatable(L, "kiwmi_output");
lua_setmetatable(L, -2);
*output_ud = obj;
return 1;
}
int
luaK_kiwmi_output_register(lua_State *L)
{
luaL_newmetatable(L, "kiwmi_output");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaC_setfuncs(L, kiwmi_output_methods, 0);
luaC_newlib(L, kiwmi_output_events);
lua_setfield(L, -2, "__events");
lua_pushcfunction(L, luaK_usertype_ref_equal);
lua_setfield(L, -2, "__eq");
lua_pushcfunction(L, luaK_kiwmi_object_gc);
lua_setfield(L, -2, "__gc");
return 0;
}

View File

@ -0,0 +1,100 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "luak/kiwmi_renderer.h"
#include <stdlib.h>
#include <string.h>
#include <lauxlib.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output.h>
#include <wlr/util/log.h>
#include "color.h"
#include "desktop/output.h"
#include "luak/lua_compat.h"
#include "luak/luak.h"
struct kiwmi_renderer {
struct wlr_renderer *wlr_renderer;
struct kiwmi_output *output;
};
static int
l_kiwmi_renderer_draw_rect(lua_State *L)
{
struct kiwmi_renderer *renderer =
(struct kiwmi_renderer *)luaL_checkudata(L, 1, "kiwmi_renderer");
luaL_checktype(L, 2, LUA_TSTRING); // color
luaL_checktype(L, 3, LUA_TNUMBER); // x
luaL_checktype(L, 4, LUA_TNUMBER); // y
luaL_checktype(L, 5, LUA_TNUMBER); // width
luaL_checktype(L, 6, LUA_TNUMBER); // height
struct wlr_renderer *wlr_renderer = renderer->wlr_renderer;
struct kiwmi_output *output = renderer->output;
struct wlr_output *wlr_output = output->wlr_output;
float color[4];
if (!color_parse(lua_tostring(L, 2), color)) {
return luaL_argerror(L, 2, "not a valid color");
}
struct wlr_box box = {
.x = lua_tonumber(L, 3),
.y = lua_tonumber(L, 4),
.width = lua_tonumber(L, 5),
.height = lua_tonumber(L, 6),
};
wlr_render_rect(wlr_renderer, &box, color, wlr_output->transform_matrix);
return 0;
}
static const luaL_Reg kiwmi_renderer_methods[] = {
{"draw_rect", l_kiwmi_renderer_draw_rect},
{NULL, NULL},
};
int
luaK_kiwmi_renderer_new(lua_State *L)
{
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA); // kiwmi_lua
luaL_checktype(L, 2, LUA_TLIGHTUSERDATA); // wlr_renderer
luaL_checktype(L, 3, LUA_TLIGHTUSERDATA); // wlr_output
struct kiwmi_lua *UNUSED(lua) = lua_touserdata(L, 1);
struct wlr_renderer *wlr_renderer = lua_touserdata(L, 2);
struct kiwmi_output *output = lua_touserdata(L, 3);
struct kiwmi_renderer *renderer_ud =
lua_newuserdata(L, sizeof(*renderer_ud));
luaL_getmetatable(L, "kiwmi_renderer");
lua_setmetatable(L, -2);
renderer_ud->wlr_renderer = wlr_renderer;
renderer_ud->output = output;
return 1;
}
int
luaK_kiwmi_renderer_register(lua_State *L)
{
luaL_newmetatable(L, "kiwmi_renderer");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaC_setfuncs(L, kiwmi_renderer_methods, 0);
lua_pushcfunction(L, luaK_usertype_ref_equal);
lua_setfield(L, -2, "__eq");
return 0;
}

View File

@ -0,0 +1,608 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "luak/kiwmi_server.h"
#include <stdlib.h>
#include <unistd.h>
#include <lauxlib.h>
#include <wayland-server.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/util/log.h>
#include "color.h"
#include "desktop/view.h"
#include "input/cursor.h"
#include "input/input.h"
#include "input/seat.h"
#include "luak/kiwmi_cursor.h"
#include "luak/kiwmi_keyboard.h"
#include "luak/kiwmi_lua_callback.h"
#include "luak/kiwmi_output.h"
#include "luak/kiwmi_view.h"
#include "luak/lua_compat.h"
#include "server.h"
static int
l_kiwmi_server_active_output(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
struct kiwmi_server *server = obj->object;
struct kiwmi_output *output = desktop_active_output(server);
lua_pushcfunction(L, luaK_kiwmi_output_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, output);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 1;
}
static int
l_kiwmi_server_bg_color(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
luaL_checktype(L, 2, LUA_TSTRING);
struct kiwmi_server *server = obj->object;
float color[4];
if (!color_parse(lua_tostring(L, 2), color)) {
return luaL_argerror(L, 2, "not a valid color");
}
server->desktop.bg_color[0] = color[0];
server->desktop.bg_color[1] = color[1];
server->desktop.bg_color[2] = color[2];
// ignore alpha
return 0;
}
static int
l_kiwmi_server_cursor(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
struct kiwmi_server *server = obj->object;
lua_pushcfunction(L, luaK_kiwmi_cursor_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, server->input.cursor);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 1;
}
static int
l_kiwmi_server_focused_view(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
struct kiwmi_server *server = obj->object;
if (!server->input.seat->focused_view) {
return 0;
}
lua_pushcfunction(L, luaK_kiwmi_view_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, server->input.seat->focused_view);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 1;
}
static int
l_kiwmi_server_output_at(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
luaL_checktype(L, 2, LUA_TNUMBER); // lx
luaL_checktype(L, 3, LUA_TNUMBER); // ly
struct kiwmi_server *server = obj->object;
double lx = lua_tonumber(L, 2);
double ly = lua_tonumber(L, 3);
struct wlr_output *wlr_output =
wlr_output_layout_output_at(server->desktop.output_layout, lx, ly);
if (wlr_output) {
lua_pushcfunction(L, luaK_kiwmi_output_new);
lua_pushlightuserdata(L, obj->lua);
lua_pushlightuserdata(L, wlr_output->data);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
} else {
lua_pushnil(L);
}
return 1;
}
static int
l_kiwmi_server_quit(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
struct kiwmi_server *server = obj->object;
wl_display_terminate(server->wl_display);
return 0;
}
static int
kiwmi_server_schedule_handler(void *data)
{
struct kiwmi_lua_callback *lc = data;
lua_State *L = lc->server->lua->L;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_pushvalue(L, -1);
if (lua_pcall(L, 1, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
}
wl_event_source_remove(lc->event_source);
luaL_unref(L, LUA_REGISTRYINDEX, lc->callback_ref);
wl_list_remove(&lc->link);
free(lc);
return 0;
}
static int
l_kiwmi_server_schedule(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
luaL_checktype(L, 2, LUA_TNUMBER); // delay
luaL_checktype(L, 3, LUA_TFUNCTION); // callback
struct kiwmi_server *server = obj->object;
struct kiwmi_lua_callback *lc = malloc(sizeof(*lc));
if (!lc) {
return luaL_error(L, "failed to allocate kiwmi_lua_callback");
}
int delay = lua_tonumber(L, 2);
lc->event_source = wl_event_loop_add_timer(
server->wl_event_loop, kiwmi_server_schedule_handler, lc);
if (wl_event_source_timer_update(lc->event_source, delay) < 0) {
free(lc);
return luaL_error(L, "failed to arm timer");
}
lc->server = server;
lc->callback_ref = luaL_ref(L, LUA_REGISTRYINDEX);
wl_list_insert(&server->lua->scheduled_callbacks, &lc->link);
return 0;
}
static int
l_kiwmi_server_set_verbosity(lua_State *L)
{
luaL_checkudata(L, 1, "kiwmi_server");
luaL_checktype(L, 2, LUA_TNUMBER);
int verbosity = lua_tointeger(L, 2);
if (verbosity < WLR_SILENT) {
verbosity = WLR_SILENT;
} else if (verbosity >= WLR_LOG_IMPORTANCE_LAST) {
verbosity = WLR_DEBUG;
}
wlr_log_init((enum wlr_log_importance)verbosity, NULL);
return 0;
}
static int
l_kiwmi_server_spawn(lua_State *L)
{
luaL_checkudata(L, 1, "kiwmi_server");
luaL_checktype(L, 2, LUA_TSTRING);
const char *command = lua_tostring(L, 2);
pid_t pid = fork();
if (pid < 0) {
return luaL_error(L, "Failed to run command (fork)");
}
if (pid == 0) {
execl("/bin/sh", "/bin/sh", "-c", command, NULL);
_exit(EXIT_FAILURE);
}
lua_pushinteger(L, pid);
return 1;
}
static int
l_kiwmi_server_stop_interactive(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
struct kiwmi_server *server = obj->object;
server->input.cursor->cursor_mode = KIWMI_CURSOR_PASSTHROUGH;
return 0;
}
static int
l_kiwmi_server_unfocus(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
struct kiwmi_server *server = obj->object;
seat_focus_view(server->input.seat, NULL);
return 0;
}
static int
l_kiwmi_server_verbosity(lua_State *L)
{
luaL_checkudata(L, 1, "kiwmi_server");
int verbosity = (int)wlr_log_get_verbosity();
lua_pushinteger(L, verbosity);
return 1;
}
static int
l_kiwmi_server_view_at(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
luaL_checktype(L, 2, LUA_TNUMBER); // lx
luaL_checktype(L, 3, LUA_TNUMBER); // ly
struct kiwmi_server *server = obj->object;
double lx = lua_tonumber(L, 2);
double ly = lua_tonumber(L, 3);
struct wlr_surface *surface;
double sx;
double sy;
struct kiwmi_view *view =
view_at(&server->desktop, lx, ly, &surface, &sx, &sy);
if (view) {
lua_pushcfunction(L, luaK_kiwmi_view_new);
lua_pushlightuserdata(L, obj->lua);
lua_pushlightuserdata(L, view);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
} else {
lua_pushnil(L);
}
return 1;
}
static const luaL_Reg kiwmi_server_methods[] = {
{"active_output", l_kiwmi_server_active_output},
{"bg_color", l_kiwmi_server_bg_color},
{"cursor", l_kiwmi_server_cursor},
{"focused_view", l_kiwmi_server_focused_view},
{"on", luaK_callback_register_dispatch},
{"output_at", l_kiwmi_server_output_at},
{"quit", l_kiwmi_server_quit},
{"schedule", l_kiwmi_server_schedule},
{"set_verbosity", l_kiwmi_server_set_verbosity},
{"spawn", l_kiwmi_server_spawn},
{"stop_interactive", l_kiwmi_server_stop_interactive},
{"unfocus", l_kiwmi_server_unfocus},
{"verbosity", l_kiwmi_server_verbosity},
{"view_at", l_kiwmi_server_view_at},
{NULL, NULL},
};
static void
kiwmi_server_on_keyboard_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_keyboard *keyboard = data;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_pushcfunction(L, luaK_kiwmi_keyboard_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, keyboard);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
if (lua_pcall(L, 1, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
static void
kiwmi_server_on_output_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_output *output = data;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_pushcfunction(L, luaK_kiwmi_output_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, output);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
if (lua_pcall(L, 1, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
static void
kiwmi_server_on_request_active_output_notify(
struct wl_listener *listener,
void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_output **output = data;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
if (lua_pcall(L, 0, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return;
}
if (!lua_isnil(L, -1)) {
struct kiwmi_object *obj;
struct kiwmi_object **objp;
if (!(objp = luaK_toudata(L, -1, "kiwmi_output"))) {
wlr_log(
WLR_ERROR,
"kiwmi_output expected, got %s",
luaL_typename(L, -1));
return;
}
obj = *objp;
if (!obj->valid) {
wlr_log(WLR_ERROR, "kiwmi_output no longer valid");
return;
}
*output = obj->object;
}
}
static void
kiwmi_server_on_view_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_view *view = data;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_pushcfunction(L, luaK_kiwmi_view_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, view);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
if (lua_pcall(L, 1, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
static int
l_kiwmi_server_on_keyboard(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
luaL_checktype(L, 2, LUA_TFUNCTION);
struct kiwmi_server *server = obj->object;
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_server_on_keyboard_notify);
lua_pushlightuserdata(L, &server->input.events.keyboard_new);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_server_on_output(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
luaL_checktype(L, 2, LUA_TFUNCTION);
struct kiwmi_server *server = obj->object;
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_server_on_output_notify);
lua_pushlightuserdata(L, &server->desktop.events.new_output);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_server_on_request_active_output(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
luaL_checktype(L, 2, LUA_TFUNCTION);
struct kiwmi_server *server = obj->object;
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_server_on_request_active_output_notify);
lua_pushlightuserdata(L, &server->desktop.events.request_active_output);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_server_on_view(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
luaL_checktype(L, 2, LUA_TFUNCTION);
struct kiwmi_server *server = obj->object;
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_server_on_view_notify);
lua_pushlightuserdata(L, &server->desktop.events.view_map);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static const luaL_Reg kiwmi_server_events[] = {
{"keyboard", l_kiwmi_server_on_keyboard},
{"output", l_kiwmi_server_on_output},
{"request_active_output", l_kiwmi_server_on_request_active_output},
{"view", l_kiwmi_server_on_view},
{NULL, NULL},
};
int
luaK_kiwmi_server_new(lua_State *L)
{
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA); // kiwmi_lua
luaL_checktype(L, 2, LUA_TLIGHTUSERDATA); // kiwmi_server
struct kiwmi_lua *lua = lua_touserdata(L, 1);
struct kiwmi_server *server = lua_touserdata(L, 2);
struct kiwmi_object *obj =
luaK_get_kiwmi_object(lua, server, &server->events.destroy);
struct kiwmi_object **server_ud = lua_newuserdata(L, sizeof(*server_ud));
luaL_getmetatable(L, "kiwmi_server");
lua_setmetatable(L, -2);
*server_ud = obj;
return 1;
}
int
luaK_kiwmi_server_register(lua_State *L)
{
luaL_newmetatable(L, "kiwmi_server");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaC_setfuncs(L, kiwmi_server_methods, 0);
luaC_newlib(L, kiwmi_server_events);
lua_setfield(L, -2, "__events");
lua_pushcfunction(L, luaK_usertype_ref_equal);
lua_setfield(L, -2, "__eq");
lua_pushcfunction(L, luaK_kiwmi_object_gc);
lua_setfield(L, -2, "__gc");
return 0;
}

View File

@ -0,0 +1,815 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "luak/kiwmi_view.h"
#include <string.h>
#include <lauxlib.h>
#include <wayland-server.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/util/edges.h>
#include <wlr/util/log.h>
#include "desktop/output.h"
#include "desktop/view.h"
#include "desktop/xdg_shell.h"
#include "input/seat.h"
#include "luak/kiwmi_lua_callback.h"
#include "luak/kiwmi_output.h"
#include "luak/kiwmi_renderer.h"
#include "luak/lua_compat.h"
#include "server.h"
static int
l_kiwmi_view_app_id(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
lua_pushstring(L, view_get_app_id(view));
return 1;
}
static int
l_kiwmi_view_close(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
view_close(view);
return 0;
}
static int
l_kiwmi_view_csd(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
luaL_checktype(L, 2, LUA_TBOOLEAN);
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
struct kiwmi_xdg_decoration *decoration = view->decoration;
if (!decoration) {
return 0;
}
enum wlr_xdg_toplevel_decoration_v1_mode mode;
if (lua_toboolean(L, 2)) {
mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
} else {
mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
}
wlr_xdg_toplevel_decoration_v1_set_mode(decoration->wlr_decoration, mode);
return 0;
}
static int
l_kiwmi_view_focus(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
struct kiwmi_desktop *desktop = view->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
struct kiwmi_seat *seat = server->input.seat;
seat_focus_view(seat, view);
return 0;
}
static int
l_kiwmi_view_hidden(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
lua_pushboolean(L, view->hidden);
return 1;
}
static int
l_kiwmi_view_hide(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
view->hidden = true;
return 0;
}
static int
l_kiwmi_view_id(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
lua_pushnumber(L, (lua_Number)(size_t)view);
return 1;
}
static int
l_kiwmi_view_imove(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
view_move(view);
return 0;
}
static int
l_kiwmi_view_iresize(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
luaL_checktype(L, 2, LUA_TTABLE);
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
enum wlr_edges edges = WLR_EDGE_NONE;
lua_pushnil(L);
while (lua_next(L, 2)) {
if (!lua_isstring(L, -1)) {
lua_pop(L, 1);
continue;
}
const char *edge = lua_tostring(L, -1);
switch (edge[0]) {
case 't':
edges |= WLR_EDGE_TOP;
break;
case 'b':
edges |= WLR_EDGE_BOTTOM;
break;
case 'l':
edges |= WLR_EDGE_LEFT;
break;
case 'r':
edges |= WLR_EDGE_RIGHT;
break;
}
lua_pop(L, 1);
}
view_resize(view, edges);
return 0;
}
static int
l_kiwmi_view_move(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
luaL_checktype(L, 2, LUA_TNUMBER);
luaL_checktype(L, 3, LUA_TNUMBER);
uint32_t x = lua_tonumber(L, 2);
uint32_t y = lua_tonumber(L, 3);
view_set_pos(view, x, y);
return 0;
}
static int
l_kiwmi_view_pid(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
lua_pushinteger(L, view_get_pid(view));
return 1;
}
static int
l_kiwmi_view_pos(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
lua_pushinteger(L, view->x);
lua_pushinteger(L, view->y);
return 2;
}
static int
l_kiwmi_view_resize(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
luaL_checktype(L, 2, LUA_TNUMBER); // w
luaL_checktype(L, 3, LUA_TNUMBER); // h
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
double w = lua_tonumber(L, 2);
double h = lua_tonumber(L, 3);
view_set_size(view, w, h);
return 0;
}
static int
l_kiwmi_view_show(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
view->hidden = false;
return 0;
}
static int
l_kiwmi_view_size(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
uint32_t width;
uint32_t height;
view_get_size(view, &width, &height);
lua_pushinteger(L, width);
lua_pushinteger(L, height);
return 2;
}
static int
l_kiwmi_view_tiled(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
if (lua_isboolean(L, 2)) {
enum wlr_edges edges = WLR_EDGE_NONE;
if (lua_toboolean(L, 2)) {
edges =
WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT;
}
view_set_tiled(view, edges);
return 0;
}
if (lua_istable(L, 2)) {
enum wlr_edges edges = WLR_EDGE_NONE;
lua_pushnil(L);
while (lua_next(L, 2)) {
if (!lua_isstring(L, -1)) {
lua_pop(L, 1);
continue;
}
const char *edge = lua_tostring(L, -1);
switch (edge[0]) {
case 't':
edges |= WLR_EDGE_TOP;
break;
case 'b':
edges |= WLR_EDGE_BOTTOM;
break;
case 'l':
edges |= WLR_EDGE_LEFT;
break;
case 'r':
edges |= WLR_EDGE_RIGHT;
break;
}
lua_pop(L, 1);
}
view_set_tiled(view, edges);
return 0;
}
return luaL_argerror(L, 2, "expected bool or table");
}
static int
l_kiwmi_view_title(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
lua_pushstring(L, view_get_title(view));
return 1;
}
static const luaL_Reg kiwmi_view_methods[] = {
{"app_id", l_kiwmi_view_app_id},
{"close", l_kiwmi_view_close},
{"csd", l_kiwmi_view_csd},
{"focus", l_kiwmi_view_focus},
{"hidden", l_kiwmi_view_hidden},
{"hide", l_kiwmi_view_hide},
{"id", l_kiwmi_view_id},
{"imove", l_kiwmi_view_imove},
{"iresize", l_kiwmi_view_iresize},
{"move", l_kiwmi_view_move},
{"on", luaK_callback_register_dispatch},
{"pid", l_kiwmi_view_pid},
{"pos", l_kiwmi_view_pos},
{"resize", l_kiwmi_view_resize},
{"show", l_kiwmi_view_show},
{"size", l_kiwmi_view_size},
{"tiled", l_kiwmi_view_tiled},
{"title", l_kiwmi_view_title},
{NULL, NULL},
};
static void
kiwmi_view_on_destroy_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_view *view = data;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_pushcfunction(L, luaK_kiwmi_view_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, view);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
if (lua_pcall(L, 1, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
static void
kiwmi_view_on_render_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_render_data *rdata = data;
struct kiwmi_view *view = rdata->data;
struct wlr_renderer *renderer = rdata->renderer;
struct kiwmi_output *output = rdata->output->data;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_newtable(L);
lua_pushcfunction(L, luaK_kiwmi_view_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, view);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
lua_setfield(L, -2, "view");
lua_pushcfunction(L, luaK_kiwmi_output_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, output);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
lua_setfield(L, -2, "output");
lua_pushcfunction(L, luaK_kiwmi_renderer_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, renderer);
lua_pushlightuserdata(L, output);
if (lua_pcall(L, 3, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
lua_setfield(L, -2, "renderer");
if (lua_pcall(L, 1, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
static void
kiwmi_view_on_request_move_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_view *view = data;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_pushcfunction(L, luaK_kiwmi_view_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, view);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
if (lua_pcall(L, 1, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
static void
kiwmi_view_on_request_resize_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_lua_callback *lc = wl_container_of(listener, lc, listener);
struct kiwmi_server *server = lc->server;
lua_State *L = server->lua->L;
struct kiwmi_request_resize_event *event = data;
struct kiwmi_view *view = event->view;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_newtable(L);
lua_pushcfunction(L, luaK_kiwmi_view_new);
lua_pushlightuserdata(L, server->lua);
lua_pushlightuserdata(L, view);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
lua_setfield(L, -2, "view");
lua_newtable(L);
int idx = 1;
if (event->edges & WLR_EDGE_TOP) {
lua_pushstring(L, "top");
lua_rawseti(L, -2, idx++);
}
if (event->edges & WLR_EDGE_BOTTOM) {
lua_pushstring(L, "bottom");
lua_rawseti(L, -2, idx++);
}
if (event->edges & WLR_EDGE_LEFT) {
lua_pushstring(L, "left");
lua_rawseti(L, -2, idx++);
}
if (event->edges & WLR_EDGE_RIGHT) {
lua_pushstring(L, "right");
lua_rawseti(L, -2, idx++);
}
lua_setfield(L, -2, "edges");
if (lua_pcall(L, 1, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
static int
l_kiwmi_view_on_destroy(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
luaL_checktype(L, 2, LUA_TFUNCTION);
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
struct kiwmi_desktop *desktop = view->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_view_on_destroy_notify);
lua_pushlightuserdata(L, &obj->events.destroy);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_view_on_post_render(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
luaL_checktype(L, 2, LUA_TFUNCTION);
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
struct kiwmi_desktop *desktop = view->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_view_on_render_notify);
lua_pushlightuserdata(L, &view->events.post_render);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_view_on_pre_render(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
luaL_checktype(L, 2, LUA_TFUNCTION);
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
struct kiwmi_desktop *desktop = view->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_view_on_render_notify);
lua_pushlightuserdata(L, &view->events.pre_render);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_view_on_request_move(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
luaL_checktype(L, 2, LUA_TFUNCTION);
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
struct kiwmi_desktop *desktop = view->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_view_on_request_move_notify);
lua_pushlightuserdata(L, &view->events.request_move);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static int
l_kiwmi_view_on_request_resize(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
luaL_checktype(L, 2, LUA_TFUNCTION);
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
struct kiwmi_desktop *desktop = view->desktop;
struct kiwmi_server *server = wl_container_of(desktop, server, desktop);
lua_pushcfunction(L, luaK_kiwmi_lua_callback_new);
lua_pushlightuserdata(L, server);
lua_pushvalue(L, 2);
lua_pushlightuserdata(L, kiwmi_view_on_request_resize_notify);
lua_pushlightuserdata(L, &view->events.request_resize);
lua_pushlightuserdata(L, obj);
if (lua_pcall(L, 5, 0, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 0;
}
static const luaL_Reg kiwmi_view_events[] = {
{"destroy", l_kiwmi_view_on_destroy},
{"post_render", l_kiwmi_view_on_post_render},
{"pre_render", l_kiwmi_view_on_pre_render},
{"request_move", l_kiwmi_view_on_request_move},
{"request_resize", l_kiwmi_view_on_request_resize},
{NULL, NULL},
};
int
luaK_kiwmi_view_new(lua_State *L)
{
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA); // kiwmi_lua
luaL_checktype(L, 2, LUA_TLIGHTUSERDATA); // kiwmi_view
struct kiwmi_lua *lua = lua_touserdata(L, 1);
struct kiwmi_view *view = lua_touserdata(L, 2);
struct kiwmi_object *obj =
luaK_get_kiwmi_object(lua, view, &view->events.unmap);
struct kiwmi_object **view_ud = lua_newuserdata(L, sizeof(*view_ud));
luaL_getmetatable(L, "kiwmi_view");
lua_setmetatable(L, -2);
*view_ud = obj;
return 1;
}
int
luaK_kiwmi_view_register(lua_State *L)
{
luaL_newmetatable(L, "kiwmi_view");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaC_setfuncs(L, kiwmi_view_methods, 0);
luaC_newlib(L, kiwmi_view_events);
lua_setfield(L, -2, "__events");
lua_pushcfunction(L, luaK_usertype_ref_equal);
lua_setfield(L, -2, "__eq");
lua_pushcfunction(L, luaK_kiwmi_object_gc);
lua_setfield(L, -2, "__gc");
return 0;
}

View File

@ -0,0 +1,24 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "luak/lua_compat.h"
#include <stdlib.h>
void
luaC_setfuncs(lua_State *L, const luaL_Reg *l, int nup)
{
luaL_checkstack(L, nup, "too many upvalues");
for (; l->name; ++l) {
for (int i = 0; i < nup; ++i) {
lua_pushvalue(L, -nup);
}
lua_pushcclosure(L, l->func, nup);
lua_setfield(L, -(nup + 2), l->name);
}
lua_pop(L, nup);
}

323
kiwmi/kiwmi/luak/luak.c Normal file
View File

@ -0,0 +1,323 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "luak/luak.h"
#include <stdlib.h>
#include <lauxlib.h>
#include <lualib.h>
#include <wlr/util/log.h>
#include "luak/ipc.h"
#include "luak/kiwmi_cursor.h"
#include "luak/kiwmi_keyboard.h"
#include "luak/kiwmi_lua_callback.h"
#include "luak/kiwmi_output.h"
#include "luak/kiwmi_renderer.h"
#include "luak/kiwmi_server.h"
#include "luak/kiwmi_view.h"
void *
luaK_toudata(lua_State *L, int ud, const char *tname)
{
void *p = lua_touserdata(L, ud);
if (p != NULL) { /* value is a userdata? */
if (lua_getmetatable(L, ud)) { /* does it have a metatable? */
lua_getfield(
L, LUA_REGISTRYINDEX, tname); /* get correct metatable */
if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */
lua_pop(L, 2); /* remove both metatables */
return p;
}
}
}
return NULL;
}
static void
kiwmi_object_destroy(struct kiwmi_object *obj)
{
wl_list_remove(&obj->destroy.link);
wl_list_remove(&obj->events.destroy.listener_list);
free(obj);
}
int
luaK_kiwmi_object_gc(lua_State *L)
{
struct kiwmi_object *obj = *(struct kiwmi_object **)lua_touserdata(L, 1);
--obj->refcount;
if (obj->refcount == 0 && wl_list_empty(&obj->callbacks)) {
kiwmi_object_destroy(obj);
}
return 0;
}
static void
kiwmi_object_destroy_notify(struct wl_listener *listener, void *data)
{
struct kiwmi_object *obj = wl_container_of(listener, obj, destroy);
wl_signal_emit(&obj->events.destroy, data);
struct kiwmi_lua_callback *lc;
struct kiwmi_lua_callback *tmp;
wl_list_for_each_safe (lc, tmp, &obj->callbacks, link) {
wl_list_remove(&lc->listener.link);
wl_list_remove(&lc->link);
luaL_unref(lc->server->lua->L, LUA_REGISTRYINDEX, lc->callback_ref);
free(lc);
}
lua_State *L = obj->lua->L;
lua_rawgeti(L, LUA_REGISTRYINDEX, obj->lua->objects);
lua_pushlightuserdata(L, obj->object);
lua_pushnil(L);
lua_settable(L, -3);
lua_pop(L, 1);
obj->valid = false;
if (obj->refcount == 0) {
kiwmi_object_destroy(obj);
}
}
struct kiwmi_object *
luaK_get_kiwmi_object(
struct kiwmi_lua *lua,
void *ptr,
struct wl_signal *destroy)
{
lua_State *L = lua->L;
lua_rawgeti(L, LUA_REGISTRYINDEX, lua->objects);
lua_pushlightuserdata(L, ptr);
lua_gettable(L, -2);
struct kiwmi_object *obj = lua_touserdata(L, -1);
lua_pop(L, 2);
if (obj) {
++obj->refcount;
return obj;
}
obj = malloc(sizeof(*obj));
if (!obj) {
wlr_log(WLR_ERROR, "Failed to allocate kiwmi_object");
return NULL;
}
obj->lua = lua;
obj->object = ptr;
obj->refcount = 1;
obj->valid = true;
if (destroy) {
obj->destroy.notify = kiwmi_object_destroy_notify;
wl_signal_add(destroy, &obj->destroy);
} else {
wl_list_init(&obj->destroy.link);
}
wl_signal_init(&obj->events.destroy);
wl_list_init(&obj->callbacks);
lua_rawgeti(L, LUA_REGISTRYINDEX, lua->objects);
lua_pushlightuserdata(L, ptr);
lua_pushlightuserdata(L, obj);
lua_settable(L, -3);
return obj;
}
int
luaK_callback_register_dispatch(lua_State *L)
{
luaL_checktype(L, 1, LUA_TUSERDATA); // object
luaL_checktype(L, 2, LUA_TSTRING); // type
luaL_checktype(L, 3, LUA_TFUNCTION); // callback
int has_mt = lua_getmetatable(L, 1);
luaL_argcheck(L, has_mt, 1, "no metatable");
lua_getfield(L, -1, "__events");
luaL_argcheck(L, lua_istable(L, -1), 1, "no __events");
lua_pushvalue(L, 2);
lua_gettable(L, -2);
luaL_argcheck(L, lua_iscfunction(L, -1), 2, "invalid event");
lua_pushvalue(L, 1);
lua_pushvalue(L, 3);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
return 0;
}
return 1;
}
int
luaK_usertype_ref_equal(lua_State *L)
{
luaL_checktype(L, 1, LUA_TUSERDATA);
luaL_checktype(L, 2, LUA_TUSERDATA);
void *a = *(void **)lua_touserdata(L, 1);
void *b = *(void **)lua_touserdata(L, 2);
lua_pushboolean(L, a == b);
return 1;
}
struct kiwmi_lua *
luaK_create(struct kiwmi_server *server)
{
struct kiwmi_lua *lua = malloc(sizeof(*lua));
if (!lua) {
wlr_log(WLR_ERROR, "Failed to allocate kiwmi_lua");
return NULL;
}
lua_State *L = luaL_newstate();
if (!L) {
free(lua);
return NULL;
}
lua->L = L;
luaL_openlibs(L);
wl_list_init(&lua->scheduled_callbacks);
// init object registry
lua_newtable(L);
lua->objects = luaL_ref(L, LUA_REGISTRYINDEX);
// register types
int error = 0;
lua_pushcfunction(L, luaK_kiwmi_cursor_register);
error |= lua_pcall(L, 0, 0, 0);
lua_pushcfunction(L, luaK_kiwmi_keyboard_register);
error |= lua_pcall(L, 0, 0, 0);
lua_pushcfunction(L, luaK_kiwmi_output_register);
error |= lua_pcall(L, 0, 0, 0);
lua_pushcfunction(L, luaK_kiwmi_renderer_register);
error |= lua_pcall(L, 0, 0, 0);
lua_pushcfunction(L, luaK_kiwmi_server_register);
error |= lua_pcall(L, 0, 0, 0);
lua_pushcfunction(L, luaK_kiwmi_view_register);
error |= lua_pcall(L, 0, 0, 0);
if (error) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_close(L);
return NULL;
}
// create FROM_KIWMIC global
lua_pushboolean(L, false);
lua_setglobal(L, "FROM_KIWMIC");
// create kiwmi global
lua_pushcfunction(L, luaK_kiwmi_server_new);
lua_pushlightuserdata(L, lua);
lua_pushlightuserdata(L, server);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
lua_close(L);
free(lua);
return NULL;
}
lua_setglobal(L, "kiwmi");
char *config_path = malloc(PATH_MAX);
if (!config_path) {
wlr_log(WLR_ERROR, "Failed to allocate memory");
lua_close(L);
free(lua);
return NULL;
}
const char *config_home = getenv("XDG_CONFIG_HOME");
if (config_home) {
snprintf(config_path, PATH_MAX, "%s/kiwmi/", config_home);
} else {
snprintf(config_path, PATH_MAX, "%s/.config/kiwmi/", getenv("HOME"));
}
lua_pushstring(L, config_path);
lua_setglobal(L, "KIWMI_CONFIGDIR");
free(config_path);
const char *adjust_package_path =
"package.path = KIWMI_CONFIGDIR .. '/?.lua;' .. package.path\n"
"package.path = KIWMI_CONFIGDIR .. '/?/init.lua;' .. package.path\n";
if (luaL_dostring(L, adjust_package_path)) {
// shouldn't fail
wlr_log(WLR_ERROR, "Error in adjust_package_path");
lua_close(L);
free(lua);
return NULL;
}
if (!luaK_ipc_init(server, lua)) {
wlr_log(WLR_ERROR, "Failed to initialize IPC");
lua_close(L);
free(lua);
return NULL;
}
return lua;
}
bool
luaK_dofile(struct kiwmi_lua *lua, const char *config_path)
{
int top = lua_gettop(lua->L);
if (luaL_dofile(lua->L, config_path)) {
wlr_log(
WLR_ERROR, "Error running config: %s", lua_tostring(lua->L, -1));
return false;
}
lua_pop(lua->L, top - lua_gettop(lua->L));
return true;
}
void
luaK_destroy(struct kiwmi_lua *lua)
{
lua_close(lua->L);
struct kiwmi_lua_callback *lc;
struct kiwmi_lua_callback *tmp;
wl_list_for_each_safe (lc, tmp, &lua->scheduled_callbacks, link) {
wl_event_source_remove(lc->event_source);
wl_list_remove(&lc->link);
free(lc);
}
free(lua);
}

95
kiwmi/kiwmi/main.c Normal file
View File

@ -0,0 +1,95 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <signal.h>
#include <unistd.h>
#include <wlr/util/log.h>
#include "server.h"
int
main(int argc, char **argv)
{
int verbosity = 0;
char *config_path = NULL;
const char *usage =
"Usage: kiwmi [options]\n"
"\n"
" -h Show help message and exit\n"
" -v Show version number and exit\n"
" -c Change config path\n"
" -V Increase verbosity level\n";
int option;
while ((option = getopt(argc, argv, "hvc:V")) != -1) {
switch (option) {
case 'h':
printf("%s", usage);
exit(EXIT_SUCCESS);
break;
case 'v':
printf("kiwmi version " KIWMI_VERSION "\n");
exit(EXIT_SUCCESS);
break;
case 'c':
config_path = strdup(optarg); // gets freed in server_fini
if (!config_path) {
fprintf(stderr, "Failed to allocate memory\n");
exit(EXIT_FAILURE);
}
break;
case 'V':
++verbosity;
break;
default:
fprintf(stderr, "%s", usage);
exit(EXIT_FAILURE);
}
}
switch (verbosity) {
case 0:
wlr_log_init(WLR_ERROR, NULL);
break;
case 1:
wlr_log_init(WLR_INFO, NULL);
break;
default:
wlr_log_init(WLR_DEBUG, NULL);
}
fprintf(stderr, "Using kiwmi v" KIWMI_VERSION "\n");
signal(SIGCHLD, SIG_IGN); // prevent zombies from kiwmi:spawn()
if (!getenv("XDG_RUNTIME_DIR")) {
wlr_log(WLR_ERROR, "XDG_RUNTIME_DIR not set");
exit(EXIT_FAILURE);
}
struct kiwmi_server server;
if (!server_init(&server, config_path)) {
wlr_log(WLR_ERROR, "Failed to initialize server");
free(config_path);
exit(EXIT_FAILURE);
}
if (!server_run(&server)) {
wlr_log(WLR_ERROR, "Failed to run server");
exit(EXIT_FAILURE);
}
server_fini(&server);
}

41
kiwmi/kiwmi/meson.build Normal file
View File

@ -0,0 +1,41 @@
kiwmi_sources = files(
'main.c',
'server.c',
'color.c',
'desktop/desktop.c',
'desktop/layer_shell.c',
'desktop/output.c',
'desktop/view.c',
'desktop/xdg_shell.c',
'input/cursor.c',
'input/input.c',
'input/keyboard.c',
'input/seat.c',
'luak/ipc.c',
'luak/kiwmi_cursor.c',
'luak/kiwmi_keyboard.c',
'luak/kiwmi_lua_callback.c',
'luak/kiwmi_output.c',
'luak/kiwmi_renderer.c',
'luak/kiwmi_server.c',
'luak/kiwmi_view.c',
'luak/lua_compat.c',
'luak/luak.c',
)
kiwmi_deps = [
lua,
pixman,
protocols_server,
wayland_server,
wlroots,
xkbcommon,
]
executable(
'kiwmi',
kiwmi_sources,
include_directories: [include],
dependencies: kiwmi_deps,
install: true,
)

153
kiwmi/kiwmi/server.c Normal file
View File

@ -0,0 +1,153 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include "server.h"
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <wayland-server.h>
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_data_control_v1.h>
#include <wlr/types/wlr_gamma_control_v1.h>
#include <wlr/types/wlr_primary_selection_v1.h>
#include <wlr/types/wlr_screencopy_v1.h>
#include <wlr/util/log.h>
#include "luak/luak.h"
bool
server_init(struct kiwmi_server *server, char *config_path)
{
wlr_log(WLR_DEBUG, "Initializing Wayland server");
server->wl_display = wl_display_create();
if (!server->wl_display) {
wlr_log(WLR_ERROR, "Failed to create display");
return false;
}
server->wl_event_loop = wl_display_get_event_loop(server->wl_display);
server->backend = wlr_backend_autocreate(server->wl_display);
if (!server->backend) {
wlr_log(WLR_ERROR, "Failed to create backend");
wl_display_destroy(server->wl_display);
return false;
}
server->renderer = wlr_renderer_autocreate(server->backend);
wlr_renderer_init_wl_display(server->renderer, server->wl_display);
server->allocator =
wlr_allocator_autocreate(server->backend, server->renderer);
wl_signal_init(&server->events.destroy);
if (!desktop_init(&server->desktop)) {
wlr_log(WLR_ERROR, "Failed to initialize desktop");
wl_display_destroy(server->wl_display);
return false;
}
if (!input_init(&server->input)) {
wlr_log(WLR_ERROR, "Failed to initialize input");
wl_display_destroy(server->wl_display);
return false;
}
wlr_data_control_manager_v1_create(server->wl_display);
wlr_gamma_control_manager_v1_create(server->wl_display);
wlr_primary_selection_v1_device_manager_create(server->wl_display);
server->socket = wl_display_add_socket_auto(server->wl_display);
if (!server->socket) {
wlr_log(WLR_ERROR, "Failed to open Wayland socket");
wl_display_destroy(server->wl_display);
return false;
}
if (!config_path) {
// default config path
config_path = realloc(config_path, PATH_MAX);
if (!config_path) {
wlr_log(WLR_ERROR, "Falied to allocate memory");
wl_display_destroy(server->wl_display);
return false;
}
const char *config_home = getenv("XDG_CONFIG_HOME");
if (config_home) {
snprintf(config_path, PATH_MAX, "%s/kiwmi/init.lua", config_home);
} else {
snprintf(
config_path,
PATH_MAX,
"%s/.config/kiwmi/init.lua",
getenv("HOME"));
}
}
wlr_screencopy_manager_v1_create(server->wl_display);
server->config_path = config_path;
if (!(server->lua = luaK_create(server))) {
wlr_log(WLR_ERROR, "Failed to initialize Lua");
wl_display_destroy(server->wl_display);
return false;
}
return true;
}
bool
server_run(struct kiwmi_server *server)
{
wlr_log(
WLR_DEBUG, "Running Wayland server on display '%s'", server->socket);
setenv("WAYLAND_DISPLAY", server->socket, true);
if (!luaK_dofile(server->lua, server->config_path)) {
wl_display_destroy(server->wl_display);
return false;
}
if (!wlr_backend_start(server->backend)) {
wlr_log(WLR_ERROR, "Failed to start backend");
wl_display_destroy(server->wl_display);
return false;
}
wl_display_run(server->wl_display);
return true;
}
void
server_fini(struct kiwmi_server *server)
{
wlr_log(WLR_DEBUG, "Shutting down Wayland server");
wl_signal_emit(&server->events.destroy, server);
wl_display_destroy_clients(server->wl_display);
desktop_fini(&server->desktop);
input_fini(&server->input);
wl_display_destroy(server->wl_display);
luaK_destroy(server->lua);
free(server->config_path);
}

103
kiwmi/kiwmic/main.c Normal file
View File

@ -0,0 +1,103 @@
/* Copyright (c), Niclas Meyer <niclas@countingsort.com>
*
* 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/.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include "kiwmi-ipc-client-protocol.h"
static void
command_done(
void *data,
struct kiwmi_command *UNUSED(kiwmi_command),
uint32_t error,
const char *message)
{
int *exit_code = data;
FILE *out;
if (error == KIWMI_COMMAND_ERROR_SUCCESS) {
*exit_code = EXIT_SUCCESS;
out = stdout;
} else {
*exit_code = EXIT_FAILURE;
out = stderr;
}
if (message[0] != '\0') {
fprintf(out, "%s\n", message);
}
}
static const struct kiwmi_command_listener command_listener = {
.done = command_done,
};
static void
registry_global(
void *data,
struct wl_registry *registry,
uint32_t name,
const char *interface,
uint32_t UNUSED(version))
{
struct kiwmi_ipc **ipc = data;
if (strcmp(interface, kiwmi_ipc_interface.name) == 0) {
*ipc = wl_registry_bind(registry, name, &kiwmi_ipc_interface, 1);
}
}
static void
registry_global_remove(
void *UNUSED(data),
struct wl_registry *UNUSED(registry),
uint32_t UNUSED(name))
{
// EMPTY
}
static const struct wl_registry_listener registry_listener = {
.global = registry_global,
.global_remove = registry_global_remove,
};
int
main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr, "Usage: kiwmic COMMAND\n");
exit(EXIT_FAILURE);
}
struct wl_display *display = wl_display_connect(NULL);
if (!display) {
fprintf(stderr, "Failed to connect to display\n");
exit(EXIT_FAILURE);
}
struct wl_registry *registry = wl_display_get_registry(display);
struct kiwmi_ipc *ipc = NULL;
wl_registry_add_listener(registry, &registry_listener, &ipc);
wl_display_roundtrip(display);
if (!ipc) {
fprintf(stderr, "Failed to bind to kiwmi_ipc\n");
exit(EXIT_FAILURE);
}
struct kiwmi_command *command = kiwmi_ipc_eval(ipc, argv[1]);
int exit_code;
kiwmi_command_add_listener(command, &command_listener, &exit_code);
wl_display_roundtrip(display);
wl_display_disconnect(display);
exit(exit_code);
}

16
kiwmi/kiwmic/meson.build Normal file
View File

@ -0,0 +1,16 @@
kiwmic_sources = files(
'main.c',
)
kiwmic_deps = [
protocols_client,
wayland_client,
]
executable(
'kiwmic',
kiwmic_sources,
include_directories: [include],
dependencies: kiwmic_deps,
install: true,
)

395
kiwmi/lua_docs.md Normal file
View File

@ -0,0 +1,395 @@
# Lua API Documentation
kiwmi is configured completely in Lua.
All types kiwmi offers are actually reference types, pointing to the actual internal types.
This means Lua's garbage collection has no effect on the lifetime of the object.
kiwmi offers the following classes to work with:
## Globals
### `FROM_KIWMIC`
`true` when invoked from kiwmic, `false` otherwise.
## kiwmi_server
This is the type of the global `kiwmi` singleton, representing the compositor.
This is the entry point to the API.
### Methods
#### kiwmi:active_output()
Returns the active `kiwmi_output`.
See `request_active_output`.
#### kiwmi:bg_color(color)
Sets the background color (shown behind all views) to `color` (in the format #rrggbb).
#### kiwmi:cursor()
Returns a reference to the cursor object.
#### kiwmi:focused_view()
Returns the currently focused view.
#### kiwmi:output_at(lx, ly)
Returns the output at a specified position
#### kiwmi:on(event, callback)
Used to register event listeners.
#### kiwmi:quit()
Quit kiwmi.
#### kiwmi:schedule(delay, callback)
Call `callback` after `delay` ms.
Callback get passed itself, so that it can easily reregister itself.
#### kiwmi:set_verbosity(level)
Sets verbosity of kiwmi to the level specified with a number (see `kiwmi:verbosity()`).
#### kiwmi:spawn(command)
Spawn a new process.
`command` is passed to `/bin/sh`.
#### kiwmi:stop_interactive()
Stops an interactive move or resize.
#### kiwmi:unfocus()
Unfocus the currently focused view.
#### kiwmi:verbosity()
Returns the numerical verbosity level of kiwmi (value of one of `wlr_log_importance`, silent = 0, error = 1, info = 2, debug = 3).
#### kiwmi:view_at(lx, ly)
Get the view at a specified position.
### Events
#### keyboard
A new keyboard got attached.
Callback receives a reference to the keyboard.
#### request_active_output
Called when the active output needs to be requested (for example because a layer-shell surface needs to be positioned).
Callback receives nothing and optionally returns a kiwmi_output.
If this isn't set or returns `nil`, the compositor defaults to the output the focused view is on, and if there is no view, the output the mouse is on.
#### output
A new output got attached.
Callback receives a reference to the output.
#### view
A new view got created (actually mapped).
Callback receives a reference to the view.
## kiwmi_cursor
A reference to the cursor object.
### Methods
#### cursor:output_at_pos()
Returns the output at the cursor position or `nil` if there is none.
#### cursor:on(event, callback)
Used to register event listeners.
#### cursor:pos()
Get the current position of the cursor.
Returns two parameters: `x` and `y`.
#### cursor:view_at_pos()
Returns the view at the cursor position, or `nil` if there is none.
### Events
#### button_down
A mouse button got pressed.
Callback receives the ID of the button (i.e. LMB is 1, RMB is 2, ...).
The callback is supposed to return `true` if the event was handled.
The compositor will not forward it to the view under the cursor.
#### button_up
A mouse button got released.
Callback receives the ID of the button (i.e. LMB is 1, RMB is 2, ...).
The callback is supposed to return `true` if the event was handled.
The compositor will not forward it to the view under the cursor.
#### motion
The cursor got moved.
Callback receives a table containing `oldx`, `oldy`, `newx`, and `newy`.
#### scroll
Something was scrolled.
The callback receives a table containing `device` with the device name, `vertical` indicating whether it was a vertical or horizontal scroll, and `length` with the length of the vector (negative for left of up scrolls).
The callback is supposed to return `true` if the event was handled.
The compositor will not forward it to the view under the cursor.
## kiwmi_keyboard
A handle to a keyboard.
### Methods
#### keyboard:keymap(keymap)
The function takes a table as parameter.
The possible table indexes are "rules, model, layout, variant, options".
All the table parameters are optional and set to the system default if not set.
For the values to set have a look at the xkbcommon library.
<https://xkbcommon.org/doc/current/structxkb__rule__names.html>
#### keyboard:modifiers()
Returns a table with the state of all modifiers.
These are: `shift`, `caps`, `ctrl`, `alt`, `mod2`, `mod3`, `super`, and `mod5`.
#### keyboard:on(event, callback)
Used to register event listeners.
### Events
#### destroy
The keyboard is getting destroyed.
Callback receives the keyboard.
#### key_down
A key got pressed.
Callback receives a table containing the `key`, `keycode`, `raw`, and the `keyboard`.
This event gets triggered twice, once with mods applied (i.e. `Shift+3` is `#`) and `raw` set to `false`, and then again with no mods applied and `raw` set to `true`.
The callback is supposed to return `true` if the event was handled.
The compositor will not forward it to the focused view in that case.
#### key_up
A key got released.
Callback receives a table containing the `key`, `keycode`, `raw`, and the `keyboard`.
This event gets triggered twice, once with mods applied (i.e. `Shift+3` is `#`) and `raw` set to `false`, and then again with no mods applied and `raw` set to `true`.
The callback is supposed to return `true` if the event was handled.
The compositor will not forward it to the focused view in that case.
## kiwmi_lua_callback
A handle to a registered callback.
## kiwmi_output
Represents an output (most often a display).
### Methods
#### output:auto()
Tells the compositor to start automatically positioning the output (this is on per default).
#### output:move(lx, ly)
Moves the output to a specified position.
This is referring to the top-left corner.
#### output:name()
The name of the output.
#### output:on(event, callbacks)
Used to register event listeners.
#### output:pos()
Get the position of the output.
Returns two parameters: `x` and `y`.
#### output:redraw()
Force the output to redraw. Useful e.g. when you know the view `pre_render`/`post_render` callbacks are going to change.
#### output:size()
Get the size of the output.
Returns two parameters: `width` and `height`.
#### output:usable_area()
Returns a table containing the `x`, `y`, `width` and `height` of the output's usable area, relative to the output's top left corner.
### Events
#### destroy
The output is getting destroyed.
Callback receives the output.
#### resize
The output is being resized.
Callback receives a table containing the `output`, the new `width`, and the new `height`.
#### usable_area_change
The usable area of this output has changed, e.g. because the output was resized or the bars around it changed.
Callback receives a table containing the `output` and the new `x`, `y`, `width` and `height`.
## kiwmi_renderer
Represents a rendering context, to draw on the output.
### Methods
#### renderer:draw_rect(color, x, y, w, h)
Draws a rect at the given position.
Color is a string in the form #rrggbb or #rrggbbaa.
## kiwmi_view
Represents a view (a window in kiwmi terms).
### Methods
#### view:app_id()
Returns the app id of the view.
This is comparable to the window class of X windows.
#### view:close()
Closes the view.
#### view:csd(client_draws)
Set whether the client is supposed to draw their own client decoration.
#### view:focus()
Focuses the view.
#### view:hidden()
Returns `true` if the view is hidden, `false` otherwise.
#### view:hide()
Hides the view.
#### view:id()
Returns an ID unique to the view.
#### view:imove()
Starts an interactive move.
#### view:iresize(edges)
Starts an interactive resize.
Takes a table containing the edges, that the resize is happening on.
#### view:move(lx, ly)
Moves the view to the specified position.
#### view:on(event, callback)
Used to register event listeners.
#### view:pid()
Returns the process ID of the client associated with the view.
#### view:pos()
Returns the position of the view (top-left corner).
#### view:resize(width, height)
Resizes the view.
#### view:show()
Unhides the view.
#### view:size()
Returns the size of the view.
**NOTE**: Used directly after `view:resize()`, this still returns the old size.
#### view:tiled(edges)
Takes a table containing all edges that are tiled, or a bool to indicate all 4 edges.
#### view:title()
Returns the title of the view.
### Events
#### destroy
The view is being destroyed.
Callback receives the view.
#### post_render
The view finished being rendered.
Callback receives a table with the `view`, the `renderer` and the `output`.
This event occurs once per output the view might be drawn on.
#### pre_render
The view is about to be rendered.
Callback receives a table with the `view`, the `renderer` and the `output`.
This event occurs once per output the view might be drawn on.
#### request_move
The view wants to start an interactive move.
Callback receives the view.
#### request_resize
The view wants to start an interactive resize.
Callback receives a table containing the `view`, and `edges`, containing the edges.

59
kiwmi/meson.build Normal file
View File

@ -0,0 +1,59 @@
project(
'kiwmi',
'c',
license: 'MPL-2.0',
default_options: [
'c_std=c11',
'warning_level=2',
],
)
add_project_arguments(
[
'-DWLR_USE_UNSTABLE',
'-D_POSIX_C_SOURCE=200809L',
],
language: 'c',
)
git = find_program('git', required: false)
lua = dependency(get_option('lua-pkg'))
pixman = dependency('pixman-1')
wayland_client = dependency('wayland-client')
wayland_protocols = dependency('wayland-protocols')
wayland_scanner = dependency('wayland-scanner')
wayland_server = dependency('wayland-server')
wlroots = dependency('wlroots')
xkbcommon = dependency('xkbcommon')
include = include_directories('include')
version = get_option('kiwmi-version')
if version != ''
version = '"@0@"'.format(version)
else
if not git.found()
error('git is required to make the version string')
endif
git_commit_hash = run_command([git.path(), 'describe', '--always', '--tags']).stdout().strip()
git_branch = run_command([git.path(), 'rev-parse', '--abbrev-ref', 'HEAD']).stdout().strip()
version = '"@0@ (" __DATE__ ", branch \'@1@\')"'.format(git_commit_hash, git_branch)
endif
add_project_arguments('-DKIWMI_VERSION=@0@'.format(version), language: 'c')
compiler = meson.get_compiler('c')
if compiler.get_id() == 'gcc' or compiler.get_id() == 'clang'
add_project_arguments('-DUNUSED(x)=UNUSED_ ## x __attribute__((__unused__))', language: 'c')
else
add_project_arguments('-DUNUSED(x)=UNUSED_ ## x', language: 'c')
endif
install_data(
'kiwmi.desktop',
install_dir: join_paths(get_option('datadir'), 'wayland-sessions')
)
subdir('protocols')
subdir('kiwmi')
subdir('kiwmic')

2
kiwmi/meson_options.txt Normal file
View File

@ -0,0 +1,2 @@
option('kiwmi-version', type: 'string', description: 'The version string reported in `kiwmi -v`.')
option('lua-pkg', type: 'string', value: 'lua', description: 'The Lua version to use.')

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="kiwmi_ipc">
<copyright>
Copyright (c), Niclas Meyer &lt;niclas@countingsort.com&gt;
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/.
</copyright>
<interface name="kiwmi_ipc" version="1">
<request name="eval">
<description summary="evaluate a given Lua snippet" />
<arg name="id" type="new_id" interface="kiwmi_command" />
<arg name="command" type="string" />
</request>
</interface>
<interface name="kiwmi_command" version="1">
<enum name="error">
<entry name="success" value="0" summary="the command ran successfully" />
<entry name="failure" value="1" summary="the command did not run successfully" />
</enum>
<event name="done">
<arg name="error" type="uint" enum="error" />
<arg name="message" type="string" summary="error message or return value" />
</event>
</interface>
</protocol>

View File

@ -0,0 +1,70 @@
wayland_protocols_dir = wayland_protocols.get_pkgconfig_variable('pkgdatadir')
wayland_scanner_bin = wayland_scanner.get_pkgconfig_variable('wayland_scanner')
protocols_server = [
wayland_protocols_dir / 'stable/xdg-shell/xdg-shell.xml',
'kiwmi-ipc.xml',
'wlr-layer-shell-unstable-v1.xml',
]
protocols_server_src = []
protocols_server_inc = []
foreach protocol : protocols_server
protocols_server_src += custom_target(
protocol.underscorify() + '_server_c',
input: protocol,
output: '@BASENAME@-protocol.c',
command: [wayland_scanner_bin, 'private-code', '@INPUT@', '@OUTPUT@'],
)
protocols_server_inc += custom_target(
protocol.underscorify() + '_server_h',
input: protocol,
output: '@BASENAME@-protocol.h',
command: [wayland_scanner_bin, 'server-header', '@INPUT@', '@OUTPUT@'],
)
endforeach
protocols_server_lib = static_library(
'protocols_server',
[protocols_server_src, protocols_server_inc],
)
protocols_server = declare_dependency(
link_with: protocols_server_lib,
sources: protocols_server_inc,
)
protocols_client = [
'kiwmi-ipc.xml',
]
protocols_client_src = []
protocols_client_inc = []
foreach protocol : protocols_client
protocols_client_src += custom_target(
protocol.underscorify() + '_client_c',
input: protocol,
output: '@BASENAME@-client-protocol.c',
command: [wayland_scanner_bin, 'private-code', '@INPUT@', '@OUTPUT@'],
)
protocols_client_inc += custom_target(
protocol.underscorify() + '_client_h',
input: protocol,
output: '@BASENAME@-client-protocol.h',
command: [wayland_scanner_bin, 'client-header', '@INPUT@', '@OUTPUT@'],
)
endforeach
protocols_client_lib = static_library(
'protocols_client',
[protocols_client_src, protocols_client_inc],
)
protocols_client = declare_dependency(
link_with: protocols_client_lib,
sources: protocols_client_inc,
)

View File

@ -0,0 +1,390 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_layer_shell_unstable_v1">
<copyright>
Copyright © 2017 Drew DeVault
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<interface name="zwlr_layer_shell_v1" version="4">
<description summary="create surfaces that are layers of the desktop">
Clients can use this interface to assign the surface_layer role to
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
rendered with a defined z-depth respective to each other. They may also be
anchored to the edges and corners of a screen and specify input handling
semantics. This interface should be suitable for the implementation of
many desktop shell components, and a broad number of other applications
that interact with the desktop.
</description>
<request name="get_layer_surface">
<description summary="create a layer_surface from a surface">
Create a layer surface for an existing surface. This assigns the role of
layer_surface, or raises a protocol error if another role is already
assigned.
Creating a layer surface from a wl_surface which has a buffer attached
or committed is a client error, and any attempts by a client to attach
or manipulate a buffer prior to the first layer_surface.configure call
must also be treated as errors.
After creating a layer_surface object and setting it up, the client
must perform an initial commit without any buffer attached.
The compositor will reply with a layer_surface.configure event.
The client must acknowledge it and is then allowed to attach a buffer
to map the surface.
You may pass NULL for output to allow the compositor to decide which
output to use. Generally this will be the one that the user most
recently interacted with.
Clients can specify a namespace that defines the purpose of the layer
surface.
</description>
<arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
<arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
<arg name="namespace" type="string" summary="namespace for the layer surface"/>
</request>
<enum name="error">
<entry name="role" value="0" summary="wl_surface has another role"/>
<entry name="invalid_layer" value="1" summary="layer value is invalid"/>
<entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
</enum>
<enum name="layer">
<description summary="available layers for surfaces">
These values indicate which layers a surface can be rendered in. They
are ordered by z depth, bottom-most first. Traditional shell surfaces
will typically be rendered between the bottom and top layers.
Fullscreen shell surfaces are typically rendered at the top layer.
Multiple surfaces can share a single layer, and ordering within a
single layer is undefined.
</description>
<entry name="background" value="0"/>
<entry name="bottom" value="1"/>
<entry name="top" value="2"/>
<entry name="overlay" value="3"/>
</enum>
<!-- Version 3 additions -->
<request name="destroy" type="destructor" since="3">
<description summary="destroy the layer_shell object">
This request indicates that the client will not use the layer_shell
object any more. Objects that have been created through this instance
are not affected.
</description>
</request>
</interface>
<interface name="zwlr_layer_surface_v1" version="4">
<description summary="layer metadata interface">
An interface that may be implemented by a wl_surface, for surfaces that
are designed to be rendered as a layer of a stacked desktop-like
environment.
Layer surface state (layer, size, anchor, exclusive zone,
margin, interactivity) is double-buffered, and will be applied at the
time wl_surface.commit of the corresponding wl_surface is called.
Attaching a null buffer to a layer surface unmaps it.
Unmapping a layer_surface means that the surface cannot be shown by the
compositor until it is explicitly mapped again. The layer_surface
returns to the state it had right after layer_shell.get_layer_surface.
The client can re-map the surface by performing a commit without any
buffer attached, waiting for a configure event and handling it as usual.
</description>
<request name="set_size">
<description summary="sets the size of the surface">
Sets the size of the surface in surface-local coordinates. The
compositor will display the surface centered with respect to its
anchors.
If you pass 0 for either value, the compositor will assign it and
inform you of the assignment in the configure event. You must set your
anchor to opposite edges in the dimensions you omit; not doing so is a
protocol error. Both values are 0 by default.
Size is double-buffered, see wl_surface.commit.
</description>
<arg name="width" type="uint"/>
<arg name="height" type="uint"/>
</request>
<request name="set_anchor">
<description summary="configures the anchor point of the surface">
Requests that the compositor anchor the surface to the specified edges
and corners. If two orthogonal edges are specified (e.g. 'top' and
'left'), then the anchor point will be the intersection of the edges
(e.g. the top left corner of the output); otherwise the anchor point
will be centered on that edge, or in the center if none is specified.
Anchor is double-buffered, see wl_surface.commit.
</description>
<arg name="anchor" type="uint" enum="anchor"/>
</request>
<request name="set_exclusive_zone">
<description summary="configures the exclusive geometry of this surface">
Requests that the compositor avoids occluding an area with other
surfaces. The compositor's use of this information is
implementation-dependent - do not assume that this region will not
actually be occluded.
A positive value is only meaningful if the surface is anchored to one
edge or an edge and both perpendicular edges. If the surface is not
anchored, anchored to only two perpendicular edges (a corner), anchored
to only two parallel edges or anchored to all edges, a positive value
will be treated the same as zero.
A positive zone is the distance from the edge in surface-local
coordinates to consider exclusive.
Surfaces that do not wish to have an exclusive zone may instead specify
how they should interact with surfaces that do. If set to zero, the
surface indicates that it would like to be moved to avoid occluding
surfaces with a positive exclusive zone. If set to -1, the surface
indicates that it would not like to be moved to accommodate for other
surfaces, and the compositor should extend it all the way to the edges
it is anchored to.
For example, a panel might set its exclusive zone to 10, so that
maximized shell surfaces are not shown on top of it. A notification
might set its exclusive zone to 0, so that it is moved to avoid
occluding the panel, but shell surfaces are shown underneath it. A
wallpaper or lock screen might set their exclusive zone to -1, so that
they stretch below or over the panel.
The default value is 0.
Exclusive zone is double-buffered, see wl_surface.commit.
</description>
<arg name="zone" type="int"/>
</request>
<request name="set_margin">
<description summary="sets a margin from the anchor point">
Requests that the surface be placed some distance away from the anchor
point on the output, in surface-local coordinates. Setting this value
for edges you are not anchored to has no effect.
The exclusive zone includes the margin.
Margin is double-buffered, see wl_surface.commit.
</description>
<arg name="top" type="int"/>
<arg name="right" type="int"/>
<arg name="bottom" type="int"/>
<arg name="left" type="int"/>
</request>
<enum name="keyboard_interactivity">
<description summary="types of keyboard interaction possible for a layer shell surface">
Types of keyboard interaction possible for layer shell surfaces. The
rationale for this is twofold: (1) some applications are not interested
in keyboard events and not allowing them to be focused can improve the
desktop experience; (2) some applications will want to take exclusive
keyboard focus.
</description>
<entry name="none" value="0">
<description summary="no keyboard focus is possible">
This value indicates that this surface is not interested in keyboard
events and the compositor should never assign it the keyboard focus.
This is the default value, set for newly created layer shell surfaces.
This is useful for e.g. desktop widgets that display information or
only have interaction with non-keyboard input devices.
</description>
</entry>
<entry name="exclusive" value="1">
<description summary="request exclusive keyboard focus">
Request exclusive keyboard focus if this surface is above the shell surface layer.
For the top and overlay layers, the seat will always give
exclusive keyboard focus to the top-most layer which has keyboard
interactivity set to exclusive. If this layer contains multiple
surfaces with keyboard interactivity set to exclusive, the compositor
determines the one receiving keyboard events in an implementation-
defined manner. In this case, no guarantee is made when this surface
will receive keyboard focus (if ever).
For the bottom and background layers, the compositor is allowed to use
normal focus semantics.
This setting is mainly intended for applications that need to ensure
they receive all keyboard events, such as a lock screen or a password
prompt.
</description>
</entry>
<entry name="on_demand" value="2" since="4">
<description summary="request regular keyboard focus semantics">
This requests the compositor to allow this surface to be focused and
unfocused by the user in an implementation-defined manner. The user
should be able to unfocus this surface even regardless of the layer
it is on.
Typically, the compositor will want to use its normal mechanism to
manage keyboard focus between layer shell surfaces with this setting
and regular toplevels on the desktop layer (e.g. click to focus).
Nevertheless, it is possible for a compositor to require a special
interaction to focus or unfocus layer shell surfaces (e.g. requiring
a click even if focus follows the mouse normally, or providing a
keybinding to switch focus between layers).
This setting is mainly intended for desktop shell components (e.g.
panels) that allow keyboard interaction. Using this option can allow
implementing a desktop shell that can be fully usable without the
mouse.
</description>
</entry>
</enum>
<request name="set_keyboard_interactivity">
<description summary="requests keyboard events">
Set how keyboard events are delivered to this surface. By default,
layer shell surfaces do not receive keyboard events; this request can
be used to change this.
This setting is inherited by child surfaces set by the get_popup
request.
Layer surfaces receive pointer, touch, and tablet events normally. If
you do not want to receive them, set the input region on your surface
to an empty region.
Keyboard interactivity is double-buffered, see wl_surface.commit.
</description>
<arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
</request>
<request name="get_popup">
<description summary="assign this layer_surface as an xdg_popup parent">
This assigns an xdg_popup's parent to this layer_surface. This popup
should have been created via xdg_surface::get_popup with the parent set
to NULL, and this request must be invoked before committing the popup's
initial state.
See the documentation of xdg_popup for more details about what an
xdg_popup is and how it is used.
</description>
<arg name="popup" type="object" interface="xdg_popup"/>
</request>
<request name="ack_configure">
<description summary="ack a configure event">
When a configure event is received, if a client commits the
surface in response to the configure event, then the client
must make an ack_configure request sometime before the commit
request, passing along the serial of the configure event.
If the client receives multiple configure events before it
can respond to one, it only has to ack the last configure event.
A client is not required to commit immediately after sending
an ack_configure request - it may even ack_configure several times
before its next surface commit.
A client may send multiple ack_configure requests before committing, but
only the last request sent before a commit indicates which configure
event the client really is responding to.
</description>
<arg name="serial" type="uint" summary="the serial from the configure event"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the layer_surface">
This request destroys the layer surface.
</description>
</request>
<event name="configure">
<description summary="suggest a surface change">
The configure event asks the client to resize its surface.
Clients should arrange their surface for the new states, and then send
an ack_configure request with the serial sent in this configure event at
some point before committing the new surface.
The client is free to dismiss all but the last configure event it
received.
The width and height arguments specify the size of the window in
surface-local coordinates.
The size is a hint, in the sense that the client is free to ignore it if
it doesn't resize, pick a smaller size (to satisfy aspect ratio or
resize in steps of NxM pixels). If the client picks a smaller size and
is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
surface will be centered on this axis.
If the width or height arguments are zero, it means the client should
decide its own window dimension.
</description>
<arg name="serial" type="uint"/>
<arg name="width" type="uint"/>
<arg name="height" type="uint"/>
</event>
<event name="closed">
<description summary="surface should be closed">
The closed event is sent by the compositor when the surface will no
longer be shown. The output may have been destroyed or the user may
have asked for it to be removed. Further changes to the surface will be
ignored. The client should destroy the resource after receiving this
event, and create a new surface if they so choose.
</description>
</event>
<enum name="error">
<entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
<entry name="invalid_size" value="1" summary="size is invalid"/>
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
<entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
</enum>
<enum name="anchor" bitfield="true">
<entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
<entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
</enum>
<!-- Version 2 additions -->
<request name="set_layer" since="2">
<description summary="change the layer of the surface">
Change the layer that the surface is rendered on.
Layer is double-buffered, see wl_surface.commit.
</description>
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
</request>
</interface>
</protocol>