diff --git a/kiwmi/.cirrus.yml b/kiwmi/.cirrus.yml new file mode 100644 index 0000000..7d7e9da --- /dev/null +++ b/kiwmi/.cirrus.yml @@ -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 diff --git a/kiwmi/.clang-format b/kiwmi/.clang-format new file mode 100644 index 0000000..b004eff --- /dev/null +++ b/kiwmi/.clang-format @@ -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 +... diff --git a/kiwmi/.editorconfig b/kiwmi/.editorconfig new file mode 100644 index 0000000..51b3955 --- /dev/null +++ b/kiwmi/.editorconfig @@ -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 diff --git a/kiwmi/.gitignore b/kiwmi/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/kiwmi/.gitignore @@ -0,0 +1 @@ +/build diff --git a/kiwmi/CONTRIBUTING.md b/kiwmi/CONTRIBUTING.md new file mode 100644 index 0000000..63e6b12 --- /dev/null +++ b/kiwmi/CONTRIBUTING.md @@ -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 +``` diff --git a/kiwmi/LICENSE b/kiwmi/LICENSE new file mode 100644 index 0000000..398385c --- /dev/null +++ b/kiwmi/LICENSE @@ -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. + diff --git a/kiwmi/README.md b/kiwmi/README.md new file mode 100644 index 0000000..47732a3 --- /dev/null +++ b/kiwmi/README.md @@ -0,0 +1,69 @@ +

kiwmi

+

A fully programmable Wayland Compositor

+

+ Stars + Build Status + GitHub Issues + GitHub Contributors +

+ +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. diff --git a/kiwmi/include/color.h b/kiwmi/include/color.h new file mode 100644 index 0000000..0d0e9f7 --- /dev/null +++ b/kiwmi/include/color.h @@ -0,0 +1,15 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +bool color_parse(const char *hex, float color[static 4]); + +#endif /* KIWMI_COLOR_H */ diff --git a/kiwmi/include/desktop/desktop.h b/kiwmi/include/desktop/desktop.h new file mode 100644 index 0000000..6fbdd35 --- /dev/null +++ b/kiwmi/include/desktop/desktop.h @@ -0,0 +1,43 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +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 */ diff --git a/kiwmi/include/desktop/layer_shell.h b/kiwmi/include/desktop/layer_shell.h new file mode 100644 index 0000000..837275e --- /dev/null +++ b/kiwmi/include/desktop/layer_shell.h @@ -0,0 +1,45 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include +#include + +#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 */ diff --git a/kiwmi/include/desktop/output.h b/kiwmi/include/desktop/output.h new file mode 100644 index 0000000..3f8c034 --- /dev/null +++ b/kiwmi/include/desktop/output.h @@ -0,0 +1,48 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include + +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 */ diff --git a/kiwmi/include/desktop/view.h b/kiwmi/include/desktop/view.h new file mode 100644 index 0000000..3055304 --- /dev/null +++ b/kiwmi/include/desktop/view.h @@ -0,0 +1,184 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include + +#include + +#include +#include +#include + +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 */ diff --git a/kiwmi/include/desktop/xdg_shell.h b/kiwmi/include/desktop/xdg_shell.h new file mode 100644 index 0000000..ddfd742 --- /dev/null +++ b/kiwmi/include/desktop/xdg_shell.h @@ -0,0 +1,25 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +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 */ diff --git a/kiwmi/include/input/cursor.h b/kiwmi/include/input/cursor.h new file mode 100644 index 0000000..9f6c570 --- /dev/null +++ b/kiwmi/include/input/cursor.h @@ -0,0 +1,82 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include + +#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 */ diff --git a/kiwmi/include/input/input.h b/kiwmi/include/input/input.h new file mode 100644 index 0000000..cbaa60d --- /dev/null +++ b/kiwmi/include/input/input.h @@ -0,0 +1,27 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +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 */ diff --git a/kiwmi/include/input/keyboard.h b/kiwmi/include/input/keyboard.h new file mode 100644 index 0000000..fd15673 --- /dev/null +++ b/kiwmi/include/input/keyboard.h @@ -0,0 +1,45 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include + +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 */ diff --git a/kiwmi/include/input/seat.h b/kiwmi/include/input/seat.h new file mode 100644 index 0000000..355a60f --- /dev/null +++ b/kiwmi/include/input/seat.h @@ -0,0 +1,38 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include + +#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 */ diff --git a/kiwmi/include/luak/ipc.h b/kiwmi/include/luak/ipc.h new file mode 100644 index 0000000..e39c75b --- /dev/null +++ b/kiwmi/include/luak/ipc.h @@ -0,0 +1,18 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include "luak/luak.h" +#include "server.h" + +bool luaK_ipc_init(struct kiwmi_server *server, struct kiwmi_lua *lua); + +#endif /* KIWMI_LUAK_IPC_H */ diff --git a/kiwmi/include/luak/kiwmi_cursor.h b/kiwmi/include/luak/kiwmi_cursor.h new file mode 100644 index 0000000..9334f97 --- /dev/null +++ b/kiwmi/include/luak/kiwmi_cursor.h @@ -0,0 +1,16 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +int luaK_kiwmi_cursor_new(lua_State *L); +int luaK_kiwmi_cursor_register(lua_State *L); + +#endif /* KIWMI_LUAK_KIWMI_CURSOR_H */ diff --git a/kiwmi/include/luak/kiwmi_keyboard.h b/kiwmi/include/luak/kiwmi_keyboard.h new file mode 100644 index 0000000..00eb736 --- /dev/null +++ b/kiwmi/include/luak/kiwmi_keyboard.h @@ -0,0 +1,16 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +int luaK_kiwmi_keyboard_new(lua_State *L); +int luaK_kiwmi_keyboard_register(lua_State *L); + +#endif /* KIWMI_LUAK_KIWMI_KEYBOARD_H */ diff --git a/kiwmi/include/luak/kiwmi_lua_callback.h b/kiwmi/include/luak/kiwmi_lua_callback.h new file mode 100644 index 0000000..3a13169 --- /dev/null +++ b/kiwmi/include/luak/kiwmi_lua_callback.h @@ -0,0 +1,27 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#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 */ diff --git a/kiwmi/include/luak/kiwmi_output.h b/kiwmi/include/luak/kiwmi_output.h new file mode 100644 index 0000000..85a664a --- /dev/null +++ b/kiwmi/include/luak/kiwmi_output.h @@ -0,0 +1,16 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +int luaK_kiwmi_output_new(lua_State *L); +int luaK_kiwmi_output_register(lua_State *L); + +#endif /* KIWMI_LUAK_KIWMI_OUTPUT_H */ diff --git a/kiwmi/include/luak/kiwmi_renderer.h b/kiwmi/include/luak/kiwmi_renderer.h new file mode 100644 index 0000000..5333fe2 --- /dev/null +++ b/kiwmi/include/luak/kiwmi_renderer.h @@ -0,0 +1,16 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +int luaK_kiwmi_renderer_new(lua_State *L); +int luaK_kiwmi_renderer_register(lua_State *L); + +#endif /* KIWMI_LUAK_KIWMI_RENDERER_H */ diff --git a/kiwmi/include/luak/kiwmi_server.h b/kiwmi/include/luak/kiwmi_server.h new file mode 100644 index 0000000..a6728c3 --- /dev/null +++ b/kiwmi/include/luak/kiwmi_server.h @@ -0,0 +1,16 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +int luaK_kiwmi_server_new(lua_State *L); +int luaK_kiwmi_server_register(lua_State *L); + +#endif /* KIWMI_LUAK_KIWMI_SERVER_H */ diff --git a/kiwmi/include/luak/kiwmi_view.h b/kiwmi/include/luak/kiwmi_view.h new file mode 100644 index 0000000..4762845 --- /dev/null +++ b/kiwmi/include/luak/kiwmi_view.h @@ -0,0 +1,16 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +int luaK_kiwmi_view_new(lua_State *L); +int luaK_kiwmi_view_register(lua_State *L); + +#endif /* KIWMI_LUAK_KIWMI_VIEW_H */ diff --git a/kiwmi/include/luak/lua_compat.h b/kiwmi/include/luak/lua_compat.h new file mode 100644 index 0000000..3487d94 --- /dev/null +++ b/kiwmi/include/luak/lua_compat.h @@ -0,0 +1,21 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include + +#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 */ diff --git a/kiwmi/include/luak/luak.h b/kiwmi/include/luak/luak.h new file mode 100644 index 0000000..e1322a2 --- /dev/null +++ b/kiwmi/include/luak/luak.h @@ -0,0 +1,52 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include + +#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 */ diff --git a/kiwmi/include/server.h b/kiwmi/include/server.h new file mode 100644 index 0000000..a4d9e7a --- /dev/null +++ b/kiwmi/include/server.h @@ -0,0 +1,38 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#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 */ diff --git a/kiwmi/kiwmi.desktop b/kiwmi/kiwmi.desktop new file mode 100644 index 0000000..5fe1294 --- /dev/null +++ b/kiwmi/kiwmi.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Name=kiwmi +Comment=A fully programmable Wayland Compositor +Exec=kiwmi +Type=Application diff --git a/kiwmi/kiwmi/color.c b/kiwmi/kiwmi/color.c new file mode 100644 index 0000000..aa12897 --- /dev/null +++ b/kiwmi/kiwmi/color.c @@ -0,0 +1,42 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include +#include + +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; +} diff --git a/kiwmi/kiwmi/desktop/desktop.c b/kiwmi/kiwmi/desktop/desktop.c new file mode 100644 index 0000000..dd9dc83 --- /dev/null +++ b/kiwmi/kiwmi/desktop/desktop.c @@ -0,0 +1,125 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/kiwmi/kiwmi/desktop/layer_shell.c b/kiwmi/kiwmi/desktop/layer_shell.c new file mode 100644 index 0000000..78e4a34 --- /dev/null +++ b/kiwmi/kiwmi/desktop/layer_shell.c @@ -0,0 +1,436 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include +#include +#include + +#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; +} diff --git a/kiwmi/kiwmi/desktop/output.c b/kiwmi/kiwmi/desktop/output.c new file mode 100644 index 0000000..6db8f62 --- /dev/null +++ b/kiwmi/kiwmi/desktop/output.c @@ -0,0 +1,383 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/kiwmi/kiwmi/desktop/view.c b/kiwmi/kiwmi/desktop/view.c new file mode 100644 index 0000000..c741d8d --- /dev/null +++ b/kiwmi/kiwmi/desktop/view.c @@ -0,0 +1,503 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include + +#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; +} diff --git a/kiwmi/kiwmi/desktop/xdg_shell.c b/kiwmi/kiwmi/desktop/xdg_shell.c new file mode 100644 index 0000000..337fd5d --- /dev/null +++ b/kiwmi/kiwmi/desktop/xdg_shell.c @@ -0,0 +1,498 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/kiwmi/kiwmi/input/cursor.c b/kiwmi/kiwmi/input/cursor.c new file mode 100644 index 0000000..7fce94a --- /dev/null +++ b/kiwmi/kiwmi/input/cursor.c @@ -0,0 +1,365 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + } +} diff --git a/kiwmi/kiwmi/input/input.c b/kiwmi/kiwmi/input/input.c new file mode 100644 index 0000000..e70159f --- /dev/null +++ b/kiwmi/kiwmi/input/input.c @@ -0,0 +1,110 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/kiwmi/kiwmi/input/keyboard.c b/kiwmi/kiwmi/input/keyboard.c new file mode 100644 index 0000000..9746b4e --- /dev/null +++ b/kiwmi/kiwmi/input/keyboard.c @@ -0,0 +1,178 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/kiwmi/kiwmi/input/seat.c b/kiwmi/kiwmi/input/seat.c new file mode 100644 index 0000000..a1ac68b --- /dev/null +++ b/kiwmi/kiwmi/input/seat.c @@ -0,0 +1,188 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/kiwmi/kiwmi/luak/ipc.c b/kiwmi/kiwmi/luak/ipc.c new file mode 100644 index 0000000..4e383ec --- /dev/null +++ b/kiwmi/kiwmi/luak/ipc.c @@ -0,0 +1,117 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include + +#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; +} diff --git a/kiwmi/kiwmi/luak/kiwmi_cursor.c b/kiwmi/kiwmi/luak/kiwmi_cursor.c new file mode 100644 index 0000000..89dcf07 --- /dev/null +++ b/kiwmi/kiwmi/luak/kiwmi_cursor.c @@ -0,0 +1,343 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include +#include +#include + +#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; +} diff --git a/kiwmi/kiwmi/luak/kiwmi_keyboard.c b/kiwmi/kiwmi/luak/kiwmi_keyboard.c new file mode 100644 index 0000000..7b162f5 --- /dev/null +++ b/kiwmi/kiwmi/luak/kiwmi_keyboard.c @@ -0,0 +1,369 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/kiwmi/kiwmi/luak/kiwmi_lua_callback.c b/kiwmi/kiwmi/luak/kiwmi_lua_callback.c new file mode 100644 index 0000000..d2ed3fe --- /dev/null +++ b/kiwmi/kiwmi/luak/kiwmi_lua_callback.c @@ -0,0 +1,44 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include + +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; +} diff --git a/kiwmi/kiwmi/luak/kiwmi_output.c b/kiwmi/kiwmi/luak/kiwmi_output.c new file mode 100644 index 0000000..e76c61c --- /dev/null +++ b/kiwmi/kiwmi/luak/kiwmi_output.c @@ -0,0 +1,424 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include +#include +#include +#include + +#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; +} diff --git a/kiwmi/kiwmi/luak/kiwmi_renderer.c b/kiwmi/kiwmi/luak/kiwmi_renderer.c new file mode 100644 index 0000000..46fcf27 --- /dev/null +++ b/kiwmi/kiwmi/luak/kiwmi_renderer.c @@ -0,0 +1,100 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include + +#include +#include +#include +#include + +#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; +} diff --git a/kiwmi/kiwmi/luak/kiwmi_server.c b/kiwmi/kiwmi/luak/kiwmi_server.c new file mode 100644 index 0000000..4e41d94 --- /dev/null +++ b/kiwmi/kiwmi/luak/kiwmi_server.c @@ -0,0 +1,608 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include + +#include +#include +#include +#include +#include + +#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; +} diff --git a/kiwmi/kiwmi/luak/kiwmi_view.c b/kiwmi/kiwmi/luak/kiwmi_view.c new file mode 100644 index 0000000..791347b --- /dev/null +++ b/kiwmi/kiwmi/luak/kiwmi_view.c @@ -0,0 +1,815 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include +#include +#include +#include + +#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; +} diff --git a/kiwmi/kiwmi/luak/lua_compat.c b/kiwmi/kiwmi/luak/lua_compat.c new file mode 100644 index 0000000..c7b2eea --- /dev/null +++ b/kiwmi/kiwmi/luak/lua_compat.c @@ -0,0 +1,24 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +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); +} diff --git a/kiwmi/kiwmi/luak/luak.c b/kiwmi/kiwmi/luak/luak.c new file mode 100644 index 0000000..f00a407 --- /dev/null +++ b/kiwmi/kiwmi/luak/luak.c @@ -0,0 +1,323 @@ +/* Copyright (c), Niclas Meyer + * + * 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 + +#include +#include +#include + +#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); +} diff --git a/kiwmi/kiwmi/main.c b/kiwmi/kiwmi/main.c new file mode 100644 index 0000000..d22154b --- /dev/null +++ b/kiwmi/kiwmi/main.c @@ -0,0 +1,95 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include +#include + +#include +#include +#include + +#include + +#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); +} diff --git a/kiwmi/kiwmi/meson.build b/kiwmi/kiwmi/meson.build new file mode 100644 index 0000000..a212eea --- /dev/null +++ b/kiwmi/kiwmi/meson.build @@ -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, +) diff --git a/kiwmi/kiwmi/server.c b/kiwmi/kiwmi/server.c new file mode 100644 index 0000000..ba0e74c --- /dev/null +++ b/kiwmi/kiwmi/server.c @@ -0,0 +1,153 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/kiwmi/kiwmic/main.c b/kiwmi/kiwmic/main.c new file mode 100644 index 0000000..9d0ee70 --- /dev/null +++ b/kiwmi/kiwmic/main.c @@ -0,0 +1,103 @@ +/* Copyright (c), Niclas Meyer + * + * 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 +#include +#include + +#include + +#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, ®istry_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); +} diff --git a/kiwmi/kiwmic/meson.build b/kiwmi/kiwmic/meson.build new file mode 100644 index 0000000..22dfe45 --- /dev/null +++ b/kiwmi/kiwmic/meson.build @@ -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, +) diff --git a/kiwmi/lua_docs.md b/kiwmi/lua_docs.md new file mode 100644 index 0000000..f04a920 --- /dev/null +++ b/kiwmi/lua_docs.md @@ -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. + + +#### 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. diff --git a/kiwmi/meson.build b/kiwmi/meson.build new file mode 100644 index 0000000..54b09d5 --- /dev/null +++ b/kiwmi/meson.build @@ -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') diff --git a/kiwmi/meson_options.txt b/kiwmi/meson_options.txt new file mode 100644 index 0000000..1096647 --- /dev/null +++ b/kiwmi/meson_options.txt @@ -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.') diff --git a/kiwmi/protocols/kiwmi-ipc.xml b/kiwmi/protocols/kiwmi-ipc.xml new file mode 100644 index 0000000..2be8464 --- /dev/null +++ b/kiwmi/protocols/kiwmi-ipc.xml @@ -0,0 +1,31 @@ + + + + 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/. + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kiwmi/protocols/meson.build b/kiwmi/protocols/meson.build new file mode 100644 index 0000000..3ab9a63 --- /dev/null +++ b/kiwmi/protocols/meson.build @@ -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, +) diff --git a/kiwmi/protocols/wlr-layer-shell-unstable-v1.xml b/kiwmi/protocols/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..d62fd51 --- /dev/null +++ b/kiwmi/protocols/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,390 @@ + + + + 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. + + + + + 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. + + + + + 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. + + + + + + + + + + + + + + + + + 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. + + + + + + + + + + + + + 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. + + + + + + + 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. + + + + + 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. + + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + + + + 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. + + + + + 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. + + + + + 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. + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + This request destroys the layer surface. + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + +