├── .gitignore ├── Cargo.toml ├── LICENSE.md └── src ├── application.rs ├── commands ├── data_device.rs ├── layer_surface.rs ├── mod.rs ├── popup.rs └── window.rs ├── conversion.rs ├── dpi.rs ├── egl.rs ├── error.rs ├── event_loop ├── control_flow.rs ├── mod.rs ├── proxy.rs └── state.rs ├── handlers ├── compositor.rs ├── data_device │ ├── data_device.rs │ ├── data_offer.rs │ ├── data_source.rs │ └── mod.rs ├── mod.rs ├── output.rs ├── seat │ ├── keyboard.rs │ ├── mod.rs │ ├── pointer.rs │ ├── seat.rs │ └── touch.rs └── shell │ ├── layer.rs │ ├── mod.rs │ ├── xdg_popup.rs │ └── xdg_window.rs ├── lib.rs ├── result.rs ├── sctk_event.rs ├── settings.rs ├── util.rs ├── widget.rs └── window.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pkg/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | .cargo/ 6 | dist/ 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iced_sctk" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [features] 9 | debug = ["iced_native/debug"] 10 | system = ["sysinfo"] 11 | application = [] 12 | multi_window = [] 13 | 14 | [dependencies] 15 | log = "0.4" 16 | thiserror = "1.0" 17 | sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "f1d9c3e" } 18 | glutin = "0.30.0-beta.2" 19 | glow = "0.11.2" 20 | raw-window-handle = "0.5.0" 21 | enum-repr = "0.2.6" 22 | futures = "0.3" 23 | wayland-backend = {version = "=0.1.0-beta.13", features = ["client_system"]} 24 | 25 | [dependencies.iced_native] 26 | version = "0.6" 27 | git = "https://github.com/pop-os/iced" 28 | branch = "sctk-cosmic" 29 | features = ["wayland"] 30 | # path = "../../cosmic-iced/native" 31 | 32 | [dependencies.iced_graphics] 33 | version = "0.4" 34 | git = "https://github.com/pop-os/iced" 35 | branch = "sctk-cosmic" 36 | # path = "../../cosmic-iced/graphics" 37 | features = ["opengl"] 38 | 39 | [dependencies.iced_futures] 40 | version = "0.5" 41 | git = "https://github.com/pop-os/iced" 42 | branch = "sctk-cosmic" 43 | # path = "../../cosmic-iced/futures" 44 | 45 | [dependencies.sysinfo] 46 | version = "0.26" 47 | optional = true 48 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | ## 1. Definitions 5 | 6 | ### 1.1. "Contributor" 7 | means each individual or legal entity that creates, contributes to 8 | the creation of, or owns Covered Software. 9 | 10 | ### 1.2. "Contributor Version" 11 | means the combination of the Contributions of others (if any) used 12 | by a Contributor and that particular Contributor's Contribution. 13 | 14 | ### 1.3. "Contribution" 15 | means Covered Software of a particular Contributor. 16 | 17 | ### 1.4. "Covered Software" 18 | means Source Code Form to which the initial Contributor has attached 19 | the notice in Exhibit A, the Executable Form of such Source Code 20 | Form, and Modifications of such Source Code Form, in each case 21 | including portions thereof. 22 | 23 | ### 1.5. "Incompatible With Secondary Licenses" 24 | means 25 | 26 | + (a) that the initial Contributor has attached the notice described 27 | in Exhibit B to the Covered Software; or 28 | 29 | + (b) that the Covered Software was made available under the terms of 30 | version 1.1 or earlier of the License, but not also under the 31 | terms of a Secondary License. 32 | 33 | ### 1.6. "Executable Form" 34 | means any form of the work other than Source Code Form. 35 | 36 | ### 1.7. "Larger Work" 37 | means a work that combines Covered Software with other material, in 38 | a separate file or files, that is not Covered Software. 39 | 40 | ### 1.8. "License" 41 | means this document. 42 | 43 | ### 1.9. "Licensable" 44 | means having the right to grant, to the maximum extent possible, 45 | whether at the time of the initial grant or subsequently, any and 46 | all of the rights conveyed by this License. 47 | 48 | ### 1.10. "Modifications" 49 | means any of the following: 50 | 51 | + (a) any file in Source Code Form that results from an addition to, 52 | deletion from, or modification of the contents of Covered 53 | Software; or 54 | 55 | + (b) any new file in Source Code Form that contains any Covered 56 | Software. 57 | 58 | ### 1.11. "Patent Claims" of a Contributor 59 | means any patent claim(s), including without limitation, method, 60 | process, and apparatus claims, in any patent Licensable by such 61 | Contributor that would be infringed, but for the grant of the 62 | License, by the making, using, selling, offering for sale, having 63 | made, import, or transfer of either its Contributions or its 64 | Contributor Version. 65 | 66 | ### 1.12. "Secondary License" 67 | means either the GNU General Public License, Version 2.0, the GNU 68 | Lesser General Public License, Version 2.1, the GNU Affero General 69 | Public License, Version 3.0, or any later versions of those 70 | licenses. 71 | 72 | ### 1.13. "Source Code Form" 73 | means the form of the work preferred for making modifications. 74 | 75 | ### 1.14. "You" (or "Your") 76 | means an individual or a legal entity exercising rights under this 77 | License. For legal entities, "You" includes any entity that 78 | controls, is controlled by, or is under common control with You. For 79 | purposes of this definition, "control" means (a) the power, direct 80 | or indirect, to cause the direction or management of such entity, 81 | whether by contract or otherwise, or (b) ownership of more than 82 | fifty percent (50%) of the outstanding shares or beneficial 83 | ownership of such entity. 84 | 85 | ## 2. License Grants and Conditions 86 | 87 | ### 2.1. Grants 88 | 89 | Each Contributor hereby grants You a world-wide, royalty-free, 90 | non-exclusive license: 91 | 92 | + (a) under intellectual property rights (other than patent or trademark) 93 | Licensable by such Contributor to use, reproduce, make available, 94 | modify, display, perform, distribute, and otherwise exploit its 95 | Contributions, either on an unmodified basis, with Modifications, or 96 | as part of a Larger Work; and 97 | 98 | + (b) under Patent Claims of such Contributor to make, use, sell, offer 99 | for sale, have made, import, and otherwise transfer either its 100 | Contributions or its Contributor Version. 101 | 102 | ### 2.2. Effective Date 103 | 104 | The licenses granted in Section 2.1 with respect to any Contribution 105 | become effective for each Contribution on the date the Contributor first 106 | distributes such Contribution. 107 | 108 | ### 2.3. Limitations on Grant Scope 109 | 110 | The licenses granted in this Section 2 are the only rights granted under 111 | this License. No additional rights or licenses will be implied from the 112 | distribution or licensing of Covered Software under this License. 113 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 114 | Contributor: 115 | 116 | + (a) for any code that a Contributor has removed from Covered Software; 117 | or 118 | 119 | + (b) for infringements caused by: (i) Your and any other third party's 120 | modifications of Covered Software, or (ii) the combination of its 121 | Contributions with other software (except as part of its Contributor 122 | Version); or 123 | 124 | + (c) under Patent Claims infringed by Covered Software in the absence of 125 | its Contributions. 126 | 127 | This License does not grant any rights in the trademarks, service marks, 128 | or logos of any Contributor (except as may be necessary to comply with 129 | the notice requirements in Section 3.4). 130 | 131 | ### 2.4. Subsequent Licenses 132 | 133 | No Contributor makes additional grants as a result of Your choice to 134 | distribute the Covered Software under a subsequent version of this 135 | License (see Section 10.2) or under the terms of a Secondary License (if 136 | permitted under the terms of Section 3.3). 137 | 138 | ### 2.5. Representation 139 | 140 | Each Contributor represents that the Contributor believes its 141 | Contributions are its original creation(s) or it has sufficient rights 142 | to grant the rights to its Contributions conveyed by this License. 143 | 144 | ### 2.6. Fair Use 145 | 146 | This License is not intended to limit any rights You have under 147 | applicable copyright doctrines of fair use, fair dealing, or other 148 | equivalents. 149 | 150 | ### 2.7. Conditions 151 | 152 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 153 | in Section 2.1. 154 | 155 | ## 3. Responsibilities 156 | 157 | ### 3.1. Distribution of Source Form 158 | 159 | All distribution of Covered Software in Source Code Form, including any 160 | Modifications that You create or to which You contribute, must be under 161 | the terms of this License. You must inform recipients that the Source 162 | Code Form of the Covered Software is governed by the terms of this 163 | License, and how they can obtain a copy of this License. You may not 164 | attempt to alter or restrict the recipients' rights in the Source Code 165 | Form. 166 | 167 | ### 3.2. Distribution of Executable Form 168 | 169 | If You distribute Covered Software in Executable Form then: 170 | 171 | + (a) such Covered Software must also be made available in Source Code 172 | Form, as described in Section 3.1, and You must inform recipients of 173 | the Executable Form how they can obtain a copy of such Source Code 174 | Form by reasonable means in a timely manner, at a charge no more 175 | than the cost of distribution to the recipient; and 176 | 177 | + (b) You may distribute such Executable Form under the terms of this 178 | License, or sublicense it under different terms, provided that the 179 | license for the Executable Form does not attempt to limit or alter 180 | the recipients' rights in the Source Code Form under this License. 181 | 182 | ### 3.3. Distribution of a Larger Work 183 | 184 | You may create and distribute a Larger Work under terms of Your choice, 185 | provided that You also comply with the requirements of this License for 186 | the Covered Software. If the Larger Work is a combination of Covered 187 | Software with a work governed by one or more Secondary Licenses, and the 188 | Covered Software is not Incompatible With Secondary Licenses, this 189 | License permits You to additionally distribute such Covered Software 190 | under the terms of such Secondary License(s), so that the recipient of 191 | the Larger Work may, at their option, further distribute the Covered 192 | Software under the terms of either this License or such Secondary 193 | License(s). 194 | 195 | ### 3.4. Notices 196 | 197 | You may not remove or alter the substance of any license notices 198 | (including copyright notices, patent notices, disclaimers of warranty, 199 | or limitations of liability) contained within the Source Code Form of 200 | the Covered Software, except that You may alter any license notices to 201 | the extent required to remedy known factual inaccuracies. 202 | 203 | ### 3.5. Application of Additional Terms 204 | 205 | You may choose to offer, and to charge a fee for, warranty, support, 206 | indemnity or liability obligations to one or more recipients of Covered 207 | Software. However, You may do so only on Your own behalf, and not on 208 | behalf of any Contributor. You must make it absolutely clear that any 209 | such warranty, support, indemnity, or liability obligation is offered by 210 | You alone, and You hereby agree to indemnify every Contributor for any 211 | liability incurred by such Contributor as a result of warranty, support, 212 | indemnity or liability terms You offer. You may include additional 213 | disclaimers of warranty and limitations of liability specific to any 214 | jurisdiction. 215 | 216 | ## 4. Inability to Comply Due to Statute or Regulation 217 | 218 | If it is impossible for You to comply with any of the terms of this 219 | License with respect to some or all of the Covered Software due to 220 | statute, judicial order, or regulation then You must: (a) comply with 221 | the terms of this License to the maximum extent possible; and (b) 222 | describe the limitations and the code they affect. Such description must 223 | be placed in a text file included with all distributions of the Covered 224 | Software under this License. Except to the extent prohibited by statute 225 | or regulation, such description must be sufficiently detailed for a 226 | recipient of ordinary skill to be able to understand it. 227 | 228 | ## 5. Termination 229 | 230 | 5.1. The rights granted under this License will terminate automatically 231 | if You fail to comply with any of its terms. However, if You become 232 | compliant, then the rights granted under this License from a particular 233 | Contributor are reinstated (a) provisionally, unless and until such 234 | Contributor explicitly and finally terminates Your grants, and (b) on an 235 | ongoing basis, if such Contributor fails to notify You of the 236 | non-compliance by some reasonable means prior to 60 days after You have 237 | come back into compliance. Moreover, Your grants from a particular 238 | Contributor are reinstated on an ongoing basis if such Contributor 239 | notifies You of the non-compliance by some reasonable means, this is the 240 | first time You have received notice of non-compliance with this License 241 | from such Contributor, and You become compliant prior to 30 days after 242 | Your receipt of the notice. 243 | 244 | 5.2. If You initiate litigation against any entity by asserting a patent 245 | infringement claim (excluding declaratory judgment actions, 246 | counter-claims, and cross-claims) alleging that a Contributor Version 247 | directly or indirectly infringes any patent, then the rights granted to 248 | You by any and all Contributors for the Covered Software under Section 249 | 2.1 of this License shall terminate. 250 | 251 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 252 | end user license agreements (excluding distributors and resellers) which 253 | have been validly granted by You or Your distributors under this License 254 | prior to termination shall survive termination. 255 | 256 | 257 | ## 6. Disclaimer of Warranty 258 | 259 | **Covered Software is provided under this License on an "as is" 260 | basis, without warranty of any kind, either expressed, implied, or 261 | statutory, including, without limitation, warranties that the 262 | Covered Software is free of defects, merchantable, fit for a 263 | particular purpose or non-infringing. The entire risk as to the 264 | quality and performance of the Covered Software is with You. 265 | Should any Covered Software prove defective in any respect, You 266 | (not any Contributor) assume the cost of any necessary servicing, 267 | repair, or correction. This disclaimer of warranty constitutes an 268 | essential part of this License. No use of any Covered Software is 269 | authorized under this License except under this disclaimer.** 270 | 271 | 272 | #7. Limitation of Liability 273 | 274 | **Under no circumstances and under no legal theory, whether tort 275 | (including negligence), contract, or otherwise, shall any 276 | Contributor, or anyone who distributes Covered Software as 277 | permitted above, be liable to You for any direct, indirect, 278 | special, incidental, or consequential damages of any character 279 | including, without limitation, damages for lost profits, loss of 280 | goodwill, work stoppage, computer failure or malfunction, or any 281 | and all other commercial damages or losses, even if such party 282 | shall have been informed of the possibility of such damages. This 283 | limitation of liability shall not apply to liability for death or 284 | personal injury resulting from such party's negligence to the 285 | extent applicable law prohibits such limitation. Some 286 | jurisdictions do not allow the exclusion or limitation of 287 | incidental or consequential damages, so this exclusion and 288 | limitation may not apply to You.** 289 | 290 | 291 | ## 8. Litigation 292 | 293 | Any litigation relating to this License may be brought only in the 294 | courts of a jurisdiction where the defendant maintains its principal 295 | place of business and such litigation shall be governed by laws of that 296 | jurisdiction, without reference to its conflict-of-law provisions. 297 | Nothing in this Section shall prevent a party's ability to bring 298 | cross-claims or counter-claims. 299 | 300 | ## 9. Miscellaneous 301 | 302 | This License represents the complete agreement concerning the subject 303 | matter hereof. If any provision of this License is held to be 304 | unenforceable, such provision shall be reformed only to the extent 305 | necessary to make it enforceable. Any law or regulation which provides 306 | that the language of a contract shall be construed against the drafter 307 | shall not be used to construe this License against a Contributor. 308 | 309 | ## 10. Versions of the License 310 | 311 | ### 10.1. New Versions 312 | 313 | Mozilla Foundation is the license steward. Except as provided in Section 314 | 10.3, no one other than the license steward has the right to modify or 315 | publish new versions of this License. Each version will be given a 316 | distinguishing version number. 317 | 318 | ### 10.2. Effect of New Versions 319 | 320 | You may distribute the Covered Software under the terms of the version 321 | of the License under which You originally received the Covered Software, 322 | or under the terms of any subsequent version published by the license 323 | steward. 324 | 325 | ### 10.3. Modified Versions 326 | 327 | If you create software not governed by this License, and you want to 328 | create a new license for such software, you may create and use a 329 | modified version of this License if you rename the license and remove 330 | any references to the name of the license steward (except to note that 331 | such modified license differs from this License). 332 | 333 | ### 10.4. Distributing Source Code Form that is Incompatible With Secondary 334 | Licenses 335 | 336 | If You choose to distribute Source Code Form that is Incompatible With 337 | Secondary Licenses under the terms of this version of the License, the 338 | notice described in Exhibit B of this License must be attached. 339 | 340 | ## Exhibit A - Source Code Form License Notice 341 | 342 | 343 | This Source Code Form is subject to the terms of the Mozilla Public 344 | License, v. 2.0. If a copy of the MPL was not distributed with this 345 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 346 | 347 | If it is not possible or desirable to put the notice in a particular 348 | file, then You may include the notice in a location (such as a LICENSE 349 | file in a relevant directory) where a recipient would be likely to look 350 | for such a notice. 351 | 352 | You may add additional accurate notices of copyright ownership. 353 | 354 | ## Exhibit B - "Incompatible With Secondary Licenses" Notice 355 | 356 | 357 | This Source Code Form is "Incompatible With Secondary Licenses", as 358 | defined by the Mozilla Public License, v. 2.0. 359 | 360 | -------------------------------------------------------------------------------- /src/commands/data_device.rs: -------------------------------------------------------------------------------- 1 | //! Interact with the data device objects of your application. 2 | -------------------------------------------------------------------------------- /src/commands/layer_surface.rs: -------------------------------------------------------------------------------- 1 | //! Interact with the window of your application. 2 | use std::marker::PhantomData; 3 | 4 | use iced_native::command::platform_specific::wayland::layer_surface::IcedMargin; 5 | use iced_native::window::Id as SurfaceId; 6 | use iced_native::{ 7 | command::{ 8 | self, 9 | platform_specific::{ 10 | self, 11 | wayland::{self, layer_surface::SctkLayerSurfaceSettings}, 12 | }, 13 | Command, 14 | }, 15 | window, 16 | }; 17 | pub use window::{Event, Mode}; 18 | 19 | pub use sctk::shell::layer::{Anchor, KeyboardInteractivity, Layer}; 20 | 21 | // TODO ASHLEY: maybe implement as builder that outputs a batched commands 22 | /// 23 | pub fn get_layer_surface(builder: SctkLayerSurfaceSettings) -> Command { 24 | Command::single(command::Action::PlatformSpecific( 25 | platform_specific::Action::Wayland(wayland::Action::LayerSurface( 26 | wayland::layer_surface::Action::LayerSurface { 27 | builder, 28 | _phantom: PhantomData::default(), 29 | }, 30 | )), 31 | )) 32 | } 33 | 34 | /// 35 | pub fn destroy_layer_surface(id: SurfaceId) -> Command { 36 | Command::single(command::Action::PlatformSpecific( 37 | platform_specific::Action::Wayland(wayland::Action::LayerSurface( 38 | wayland::layer_surface::Action::Destroy(id), 39 | )), 40 | )) 41 | } 42 | 43 | /// 44 | pub fn set_size( 45 | id: SurfaceId, 46 | width: Option, 47 | height: Option, 48 | ) -> Command { 49 | Command::single(command::Action::PlatformSpecific( 50 | platform_specific::Action::Wayland(wayland::Action::LayerSurface( 51 | wayland::layer_surface::Action::Size { id, width, height }, 52 | )), 53 | )) 54 | } 55 | /// 56 | pub fn set_anchor(id: SurfaceId, anchor: Anchor) -> Command { 57 | Command::single(command::Action::PlatformSpecific( 58 | platform_specific::Action::Wayland(wayland::Action::LayerSurface( 59 | wayland::layer_surface::Action::Anchor { id, anchor }, 60 | )), 61 | )) 62 | } 63 | /// 64 | pub fn set_exclusive_zone(id: SurfaceId, zone: i32) -> Command { 65 | Command::single(command::Action::PlatformSpecific( 66 | platform_specific::Action::Wayland(wayland::Action::LayerSurface( 67 | wayland::layer_surface::Action::ExclusiveZone { 68 | id, 69 | exclusive_zone: zone, 70 | }, 71 | )), 72 | )) 73 | } 74 | 75 | /// 76 | pub fn set_margin( 77 | id: SurfaceId, 78 | top: i32, 79 | right: i32, 80 | bottom: i32, 81 | left: i32, 82 | ) -> Command { 83 | Command::single(command::Action::PlatformSpecific( 84 | platform_specific::Action::Wayland(wayland::Action::LayerSurface( 85 | wayland::layer_surface::Action::Margin { 86 | id, 87 | margin: IcedMargin { 88 | top, 89 | right, 90 | bottom, 91 | left, 92 | }, 93 | }, 94 | )), 95 | )) 96 | } 97 | 98 | /// 99 | pub fn set_keyboard_interactivity( 100 | id: SurfaceId, 101 | keyboard_interactivity: KeyboardInteractivity, 102 | ) -> Command { 103 | Command::single(command::Action::PlatformSpecific( 104 | platform_specific::Action::Wayland(wayland::Action::LayerSurface( 105 | wayland::layer_surface::Action::KeyboardInteractivity { 106 | id, 107 | keyboard_interactivity, 108 | }, 109 | )), 110 | )) 111 | } 112 | 113 | /// 114 | pub fn set_layer(id: SurfaceId, layer: Layer) -> Command { 115 | Command::single(command::Action::PlatformSpecific( 116 | platform_specific::Action::Wayland(wayland::Action::LayerSurface( 117 | wayland::layer_surface::Action::Layer { id, layer }, 118 | )), 119 | )) 120 | } 121 | -------------------------------------------------------------------------------- /src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | //! Interact with the wayland objects of your application. 2 | 3 | pub mod data_device; 4 | pub mod layer_surface; 5 | pub mod popup; 6 | pub mod window; 7 | -------------------------------------------------------------------------------- /src/commands/popup.rs: -------------------------------------------------------------------------------- 1 | //! Interact with the popups of your application. 2 | use iced_native::command::{ 3 | self, 4 | platform_specific::{ 5 | self, 6 | wayland::{ 7 | self, 8 | popup::{SctkPopupSettings, SctkPositioner}, 9 | }, 10 | }, 11 | }; 12 | use iced_native::window::Id as SurfaceId; 13 | use iced_native::{command::Command, window}; 14 | pub use window::{Event, Mode}; 15 | 16 | /// 17 | /// 18 | pub fn get_popup(popup: SctkPopupSettings) -> Command { 19 | Command::single(command::Action::PlatformSpecific( 20 | platform_specific::Action::Wayland(wayland::Action::Popup(wayland::popup::Action::Popup { 21 | popup, 22 | _phantom: Default::default(), 23 | })), 24 | )) 25 | } 26 | 27 | /// 28 | pub fn reposition_popup(id: SurfaceId, positioner: SctkPositioner) -> Command { 29 | Command::single(command::Action::PlatformSpecific( 30 | platform_specific::Action::Wayland(wayland::Action::Popup( 31 | wayland::popup::Action::Reposition { id, positioner }, 32 | )), 33 | )) 34 | } 35 | 36 | // https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab 37 | pub fn grab_popup(id: SurfaceId) -> Command { 38 | Command::single(command::Action::PlatformSpecific( 39 | platform_specific::Action::Wayland(wayland::Action::Popup(wayland::popup::Action::Grab { 40 | id, 41 | })), 42 | )) 43 | } 44 | 45 | /// 46 | pub fn destroy_popup(id: SurfaceId) -> Command { 47 | Command::single(command::Action::PlatformSpecific( 48 | platform_specific::Action::Wayland(wayland::Action::Popup( 49 | wayland::popup::Action::Destroy { id }, 50 | )), 51 | )) 52 | } 53 | -------------------------------------------------------------------------------- /src/commands/window.rs: -------------------------------------------------------------------------------- 1 | //! Interact with the window of your application. 2 | use std::marker::PhantomData; 3 | 4 | use crate::command::{self, Command}; 5 | use iced_native::command::platform_specific::{ 6 | self, 7 | wayland::{self, window::SctkWindowSettings}, 8 | }; 9 | use iced_native::window; 10 | 11 | pub use window::Action; 12 | pub use window::{Event, Mode}; 13 | 14 | pub fn get_window(builder: SctkWindowSettings) -> Command { 15 | Command::single(command::Action::PlatformSpecific( 16 | platform_specific::Action::Wayland(wayland::Action::Window( 17 | wayland::window::Action::Window { 18 | builder, 19 | _phantom: PhantomData::default(), 20 | }, 21 | )), 22 | )) 23 | } 24 | 25 | // TODO Ashley refactor to use regular window events maybe... 26 | /// close the window 27 | pub fn close_window(id: window::Id) -> Command { 28 | Command::single(command::Action::PlatformSpecific( 29 | platform_specific::Action::Wayland(wayland::Action::Window( 30 | wayland::window::Action::Destroy(id), 31 | )), 32 | )) 33 | } 34 | 35 | /// Resizes the window to the given logical dimensions. 36 | pub fn resize_window(id: window::Id, width: u32, height: u32) -> Command { 37 | Command::single(command::Action::PlatformSpecific( 38 | platform_specific::Action::Wayland(wayland::Action::Window( 39 | wayland::window::Action::Size { id, width, height }, 40 | )), 41 | )) 42 | } 43 | 44 | /// Sets the [`Mode`] of the window. 45 | pub fn set_mode_window(id: window::Id, mode: Mode) -> Command { 46 | Command::single(command::Action::Window(id, Action::SetMode(mode))) 47 | } 48 | 49 | /// Fetches the current [`Mode`] of the window. 50 | pub fn fetch_mode_window( 51 | id: window::Id, 52 | f: impl FnOnce(Mode) -> Message + 'static, 53 | ) -> Command { 54 | Command::single(command::Action::Window(id, Action::FetchMode(Box::new(f)))) 55 | } 56 | -------------------------------------------------------------------------------- /src/conversion.rs: -------------------------------------------------------------------------------- 1 | use iced_native::{ 2 | keyboard::{self, KeyCode}, 3 | mouse::{self, ScrollDelta}, 4 | }; 5 | use sctk::{ 6 | reexports::client::protocol::wl_pointer::AxisSource, 7 | seat::{keyboard::Modifiers, pointer::AxisScroll}, 8 | }; 9 | /// An error that occurred while running an application. 10 | #[derive(Debug, thiserror::Error)] 11 | #[error("the futures executor could not be created")] 12 | pub struct KeyCodeError(u32); 13 | 14 | pub fn pointer_button_to_native(button: u32) -> Option { 15 | match button { 16 | BTN_LEFT => Some(mouse::Button::Left), 17 | BTN_MIDDLE => Some(mouse::Button::Middle), 18 | BTN_RIGHT => Some(mouse::Button::Right), 19 | b => b.try_into().ok().map(|b| mouse::Button::Other(b)), 20 | } 21 | } 22 | 23 | pub fn pointer_axis_to_native( 24 | source: Option, 25 | horizontal: AxisScroll, 26 | vertical: AxisScroll, 27 | ) -> Option { 28 | source.map(|source| match source { 29 | AxisSource::Wheel | AxisSource::WheelTilt => ScrollDelta::Lines { 30 | x: horizontal.discrete as f32, 31 | y: vertical.discrete as f32, 32 | }, 33 | AxisSource::Finger | AxisSource::Continuous | _ => ScrollDelta::Pixels { 34 | x: horizontal.absolute as f32, 35 | y: vertical.absolute as f32, 36 | }, 37 | }) 38 | } 39 | 40 | pub fn modifiers_to_native(mods: Modifiers) -> keyboard::Modifiers { 41 | let mut native_mods = keyboard::Modifiers::empty(); 42 | if mods.alt { 43 | native_mods = native_mods.union(keyboard::Modifiers::ALT); 44 | } 45 | if mods.ctrl { 46 | native_mods = native_mods.union(keyboard::Modifiers::CTRL); 47 | } 48 | if mods.logo { 49 | native_mods = native_mods.union(keyboard::Modifiers::LOGO); 50 | } 51 | if mods.shift { 52 | native_mods = native_mods.union(keyboard::Modifiers::SHIFT); 53 | } 54 | // TODO Ashley: missing modifiers as platform specific additions? 55 | // if mods.caps_lock { 56 | // native_mods = native_mods.union(keyboard::Modifier); 57 | // } 58 | // if mods.num_lock { 59 | // native_mods = native_mods.union(keyboard::Modifiers::); 60 | // } 61 | native_mods 62 | } 63 | 64 | pub fn keysym_to_vkey(keysym: u32) -> Option { 65 | use sctk::seat::keyboard::keysyms; 66 | match keysym { 67 | // Numbers. 68 | keysyms::XKB_KEY_1 => Some(KeyCode::Key1), 69 | keysyms::XKB_KEY_2 => Some(KeyCode::Key2), 70 | keysyms::XKB_KEY_3 => Some(KeyCode::Key3), 71 | keysyms::XKB_KEY_4 => Some(KeyCode::Key4), 72 | keysyms::XKB_KEY_5 => Some(KeyCode::Key5), 73 | keysyms::XKB_KEY_6 => Some(KeyCode::Key6), 74 | keysyms::XKB_KEY_7 => Some(KeyCode::Key7), 75 | keysyms::XKB_KEY_8 => Some(KeyCode::Key8), 76 | keysyms::XKB_KEY_9 => Some(KeyCode::Key9), 77 | keysyms::XKB_KEY_0 => Some(KeyCode::Key0), 78 | // Letters. 79 | keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(KeyCode::A), 80 | keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(KeyCode::B), 81 | keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(KeyCode::C), 82 | keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(KeyCode::D), 83 | keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(KeyCode::E), 84 | keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(KeyCode::F), 85 | keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(KeyCode::G), 86 | keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(KeyCode::H), 87 | keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(KeyCode::I), 88 | keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(KeyCode::J), 89 | keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(KeyCode::K), 90 | keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(KeyCode::L), 91 | keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(KeyCode::M), 92 | keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(KeyCode::N), 93 | keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(KeyCode::O), 94 | keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(KeyCode::P), 95 | keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(KeyCode::Q), 96 | keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(KeyCode::R), 97 | keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(KeyCode::S), 98 | keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(KeyCode::T), 99 | keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(KeyCode::U), 100 | keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(KeyCode::V), 101 | keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(KeyCode::W), 102 | keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(KeyCode::X), 103 | keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(KeyCode::Y), 104 | keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(KeyCode::Z), 105 | // Escape. 106 | keysyms::XKB_KEY_Escape => Some(KeyCode::Escape), 107 | // Function keys. 108 | keysyms::XKB_KEY_F1 => Some(KeyCode::F1), 109 | keysyms::XKB_KEY_F2 => Some(KeyCode::F2), 110 | keysyms::XKB_KEY_F3 => Some(KeyCode::F3), 111 | keysyms::XKB_KEY_F4 => Some(KeyCode::F4), 112 | keysyms::XKB_KEY_F5 => Some(KeyCode::F5), 113 | keysyms::XKB_KEY_F6 => Some(KeyCode::F6), 114 | keysyms::XKB_KEY_F7 => Some(KeyCode::F7), 115 | keysyms::XKB_KEY_F8 => Some(KeyCode::F8), 116 | keysyms::XKB_KEY_F9 => Some(KeyCode::F9), 117 | keysyms::XKB_KEY_F10 => Some(KeyCode::F10), 118 | keysyms::XKB_KEY_F11 => Some(KeyCode::F11), 119 | keysyms::XKB_KEY_F12 => Some(KeyCode::F12), 120 | keysyms::XKB_KEY_F13 => Some(KeyCode::F13), 121 | keysyms::XKB_KEY_F14 => Some(KeyCode::F14), 122 | keysyms::XKB_KEY_F15 => Some(KeyCode::F15), 123 | keysyms::XKB_KEY_F16 => Some(KeyCode::F16), 124 | keysyms::XKB_KEY_F17 => Some(KeyCode::F17), 125 | keysyms::XKB_KEY_F18 => Some(KeyCode::F18), 126 | keysyms::XKB_KEY_F19 => Some(KeyCode::F19), 127 | keysyms::XKB_KEY_F20 => Some(KeyCode::F20), 128 | keysyms::XKB_KEY_F21 => Some(KeyCode::F21), 129 | keysyms::XKB_KEY_F22 => Some(KeyCode::F22), 130 | keysyms::XKB_KEY_F23 => Some(KeyCode::F23), 131 | keysyms::XKB_KEY_F24 => Some(KeyCode::F24), 132 | // Flow control. 133 | keysyms::XKB_KEY_Print => Some(KeyCode::Snapshot), 134 | keysyms::XKB_KEY_Scroll_Lock => Some(KeyCode::Scroll), 135 | keysyms::XKB_KEY_Pause => Some(KeyCode::Pause), 136 | keysyms::XKB_KEY_Insert => Some(KeyCode::Insert), 137 | keysyms::XKB_KEY_Home => Some(KeyCode::Home), 138 | keysyms::XKB_KEY_Delete => Some(KeyCode::Delete), 139 | keysyms::XKB_KEY_End => Some(KeyCode::End), 140 | keysyms::XKB_KEY_Page_Down => Some(KeyCode::PageDown), 141 | keysyms::XKB_KEY_Page_Up => Some(KeyCode::PageUp), 142 | // Arrows. 143 | keysyms::XKB_KEY_Left => Some(KeyCode::Left), 144 | keysyms::XKB_KEY_Up => Some(KeyCode::Up), 145 | keysyms::XKB_KEY_Right => Some(KeyCode::Right), 146 | keysyms::XKB_KEY_Down => Some(KeyCode::Down), 147 | 148 | keysyms::XKB_KEY_BackSpace => Some(KeyCode::Backspace), 149 | keysyms::XKB_KEY_Return => Some(KeyCode::Enter), 150 | keysyms::XKB_KEY_space => Some(KeyCode::Space), 151 | 152 | keysyms::XKB_KEY_Multi_key => Some(KeyCode::Compose), 153 | keysyms::XKB_KEY_caret => Some(KeyCode::Caret), 154 | 155 | // Keypad. 156 | keysyms::XKB_KEY_Num_Lock => Some(KeyCode::Numlock), 157 | keysyms::XKB_KEY_KP_0 => Some(KeyCode::Numpad0), 158 | keysyms::XKB_KEY_KP_1 => Some(KeyCode::Numpad1), 159 | keysyms::XKB_KEY_KP_2 => Some(KeyCode::Numpad2), 160 | keysyms::XKB_KEY_KP_3 => Some(KeyCode::Numpad3), 161 | keysyms::XKB_KEY_KP_4 => Some(KeyCode::Numpad4), 162 | keysyms::XKB_KEY_KP_5 => Some(KeyCode::Numpad5), 163 | keysyms::XKB_KEY_KP_6 => Some(KeyCode::Numpad6), 164 | keysyms::XKB_KEY_KP_7 => Some(KeyCode::Numpad7), 165 | keysyms::XKB_KEY_KP_8 => Some(KeyCode::Numpad8), 166 | keysyms::XKB_KEY_KP_9 => Some(KeyCode::Numpad9), 167 | // Misc. 168 | // => Some(KeyCode::AbntC1), 169 | // => Some(KeyCode::AbntC2), 170 | keysyms::XKB_KEY_plus => Some(KeyCode::Plus), 171 | keysyms::XKB_KEY_apostrophe => Some(KeyCode::Apostrophe), 172 | // => Some(KeyCode::Apps), 173 | keysyms::XKB_KEY_at => Some(KeyCode::At), 174 | // => Some(KeyCode::Ax), 175 | keysyms::XKB_KEY_backslash => Some(KeyCode::Backslash), 176 | keysyms::XKB_KEY_XF86Calculator => Some(KeyCode::Calculator), 177 | // => Some(KeyCode::Capital), 178 | keysyms::XKB_KEY_colon => Some(KeyCode::Colon), 179 | keysyms::XKB_KEY_comma => Some(KeyCode::Comma), 180 | // => Some(KeyCode::Convert), 181 | keysyms::XKB_KEY_equal => Some(KeyCode::Equals), 182 | keysyms::XKB_KEY_grave => Some(KeyCode::Grave), 183 | // => Some(KeyCode::Kana), 184 | keysyms::XKB_KEY_Kanji => Some(KeyCode::Kanji), 185 | keysyms::XKB_KEY_Alt_L => Some(KeyCode::LAlt), 186 | keysyms::XKB_KEY_bracketleft => Some(KeyCode::LBracket), 187 | keysyms::XKB_KEY_Control_L => Some(KeyCode::LControl), 188 | keysyms::XKB_KEY_Shift_L => Some(KeyCode::LShift), 189 | keysyms::XKB_KEY_Super_L => Some(KeyCode::LWin), 190 | keysyms::XKB_KEY_XF86Mail => Some(KeyCode::Mail), 191 | // => Some(KeyCode::MediaSelect), 192 | // => Some(KeyCode::MediaStop), 193 | keysyms::XKB_KEY_minus => Some(KeyCode::Minus), 194 | keysyms::XKB_KEY_asterisk => Some(KeyCode::Asterisk), 195 | keysyms::XKB_KEY_XF86AudioMute => Some(KeyCode::Mute), 196 | // => Some(KeyCode::MyComputer), 197 | keysyms::XKB_KEY_XF86AudioNext => Some(KeyCode::NextTrack), 198 | // => Some(KeyCode::NoConvert), 199 | keysyms::XKB_KEY_KP_Separator => Some(KeyCode::NumpadComma), 200 | keysyms::XKB_KEY_KP_Enter => Some(KeyCode::NumpadEnter), 201 | keysyms::XKB_KEY_KP_Equal => Some(KeyCode::NumpadEquals), 202 | keysyms::XKB_KEY_KP_Add => Some(KeyCode::NumpadAdd), 203 | keysyms::XKB_KEY_KP_Subtract => Some(KeyCode::NumpadSubtract), 204 | keysyms::XKB_KEY_KP_Multiply => Some(KeyCode::NumpadMultiply), 205 | keysyms::XKB_KEY_KP_Divide => Some(KeyCode::NumpadDivide), 206 | keysyms::XKB_KEY_KP_Decimal => Some(KeyCode::NumpadDecimal), 207 | keysyms::XKB_KEY_KP_Page_Up => Some(KeyCode::PageUp), 208 | keysyms::XKB_KEY_KP_Page_Down => Some(KeyCode::PageDown), 209 | keysyms::XKB_KEY_KP_Home => Some(KeyCode::Home), 210 | keysyms::XKB_KEY_KP_End => Some(KeyCode::End), 211 | keysyms::XKB_KEY_KP_Left => Some(KeyCode::Left), 212 | keysyms::XKB_KEY_KP_Up => Some(KeyCode::Up), 213 | keysyms::XKB_KEY_KP_Right => Some(KeyCode::Right), 214 | keysyms::XKB_KEY_KP_Down => Some(KeyCode::Down), 215 | // => Some(KeyCode::OEM102), 216 | keysyms::XKB_KEY_period => Some(KeyCode::Period), 217 | // => Some(KeyCode::Playpause), 218 | keysyms::XKB_KEY_XF86PowerOff => Some(KeyCode::Power), 219 | keysyms::XKB_KEY_XF86AudioPrev => Some(KeyCode::PrevTrack), 220 | keysyms::XKB_KEY_Alt_R => Some(KeyCode::RAlt), 221 | keysyms::XKB_KEY_bracketright => Some(KeyCode::RBracket), 222 | keysyms::XKB_KEY_Control_R => Some(KeyCode::RControl), 223 | keysyms::XKB_KEY_Shift_R => Some(KeyCode::RShift), 224 | keysyms::XKB_KEY_Super_R => Some(KeyCode::RWin), 225 | keysyms::XKB_KEY_semicolon => Some(KeyCode::Semicolon), 226 | keysyms::XKB_KEY_slash => Some(KeyCode::Slash), 227 | keysyms::XKB_KEY_XF86Sleep => Some(KeyCode::Sleep), 228 | // => Some(KeyCode::Stop), 229 | // => Some(KeyCode::Sysrq), 230 | keysyms::XKB_KEY_Tab => Some(KeyCode::Tab), 231 | keysyms::XKB_KEY_ISO_Left_Tab => Some(KeyCode::Tab), 232 | keysyms::XKB_KEY_underscore => Some(KeyCode::Underline), 233 | // => Some(KeyCode::Unlabeled), 234 | keysyms::XKB_KEY_XF86AudioLowerVolume => Some(KeyCode::VolumeDown), 235 | keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(KeyCode::VolumeUp), 236 | // => Some(KeyCode::Wake), 237 | // => Some(KeyCode::Webback), 238 | // => Some(KeyCode::WebFavorites), 239 | // => Some(KeyCode::WebForward), 240 | // => Some(KeyCode::WebHome), 241 | // => Some(KeyCode::WebRefresh), 242 | // => Some(KeyCode::WebSearch), 243 | // => Some(KeyCode::WebStop), 244 | keysyms::XKB_KEY_yen => Some(KeyCode::Yen), 245 | keysyms::XKB_KEY_XF86Copy => Some(KeyCode::Copy), 246 | keysyms::XKB_KEY_XF86Paste => Some(KeyCode::Paste), 247 | keysyms::XKB_KEY_XF86Cut => Some(KeyCode::Cut), 248 | // Fallback. 249 | _ => None, 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/dpi.rs: -------------------------------------------------------------------------------- 1 | //! UI scaling is important, so read the docs for this module if you don't want to be confused. 2 | //! 3 | //! ## Why should I care about UI scaling? 4 | //! 5 | //! Modern computer screens don't have a consistent relationship between resolution and size. 6 | //! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens 7 | //! normally being less than a quarter the size of their desktop counterparts. What's more, neither 8 | //! desktop nor mobile screens are consistent resolutions within their own size classes - common 9 | //! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K 10 | //! and beyond. 11 | //! 12 | //! Given that, it's a mistake to assume that 2D content will only be displayed on screens with 13 | //! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen, 14 | //! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up 15 | //! about a quarter of the physical space as it did on the 1080p screen. That issue is especially 16 | //! problematic with text rendering, where quarter-sized text becomes a significant legibility 17 | //! problem. 18 | //! 19 | //! Failure to account for the scale factor can create a significantly degraded user experience. 20 | //! Most notably, it can make users feel like they have bad eyesight, which will potentially cause 21 | //! them to think about growing elderly, resulting in them having an existential crisis. Once users 22 | //! enter that state, they will no longer be focused on your application. 23 | //! 24 | //! ## How should I handle it? 25 | //! 26 | //! The solution to this problem is to account for the device's *scale factor*. The scale factor is 27 | //! the factor UI elements should be scaled by to be consistent with the rest of the user's system - 28 | //! for example, a button that's normally 50 pixels across would be 100 pixels across on a device 29 | //! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`. 30 | //! 31 | //! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's 32 | //! usually a mistake, since there's no consistent mapping between the scale factor and the screen's 33 | //! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather 34 | //! than any DPI-dependent units. 35 | //! 36 | //! ### Position and Size types 37 | //! 38 | //! Winit's [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the 39 | //! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels 40 | //! divided by the scale factor. 41 | //! All of Winit's functions return physical types, but can take either logical or physical 42 | //! coordinates as input, allowing you to use the most convenient coordinate system for your 43 | //! particular application. 44 | //! 45 | //! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the 46 | //! API to have integer precision where appropriate (e.g. most window manipulation functions) and 47 | //! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch 48 | //! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so 49 | //! will truncate the fractional part of the float, rather than properly round to the nearest 50 | //! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the 51 | //! rounding properly. Note that precision loss will still occur when rounding from a float to an 52 | //! int, although rounding lessens the problem. 53 | //! 54 | //! ### Events 55 | //! 56 | //! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed. 57 | //! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI 58 | //! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your 59 | //! application's UI elements and adjust how the platform changes the window's size to reflect the new 60 | //! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor 61 | //! can be found by calling [`window.scale_factor()`]. 62 | //! 63 | //! ## How is the scale factor calculated? 64 | //! 65 | //! Scale factor is calculated differently on different platforms: 66 | //! 67 | //! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the 68 | //! display settings. While users are free to select any option they want, they're only given a 69 | //! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is 70 | //! global and changing it requires logging out. See [this article][windows_1] for technical 71 | //! details. 72 | //! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain 73 | //! displays. When this is available, the user may pick a per-monitor scaling factor from a set 74 | //! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but 75 | //! the specific value varies across devices. 76 | //! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit 77 | //! currently uses a three-pronged approach: 78 | //! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present. 79 | //! + If not present, use the value set in `Xft.dpi` in Xresources. 80 | //! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR. 81 | //! 82 | //! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the 83 | //! XRandR scaling method. Generally speaking, you should try to configure the standard system 84 | //! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`. 85 | //! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always 86 | //! integers (most often 1 or 2). 87 | //! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range 88 | //! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more 89 | //! information. 90 | //! - **Android:** Scale factors are set by the manufacturer to the value that best suits the 91 | //! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information. 92 | //! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels. 93 | //! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by 94 | //! both the screen scaling and the browser zoom level and can go below `1.0`. 95 | //! 96 | //! 97 | //! [points]: https://en.wikipedia.org/wiki/Point_(typography) 98 | //! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) 99 | //! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged 100 | //! [`window.scale_factor()`]: crate::window::Window::scale_factor 101 | //! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows 102 | //! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html 103 | //! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/ 104 | //! [android_1]: https://developer.android.com/training/multiscreen/screendensities 105 | //! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio 106 | 107 | pub trait Pixel: Copy + Into { 108 | fn from_f64(f: f64) -> Self; 109 | fn cast(self) -> P { 110 | P::from_f64(self.into()) 111 | } 112 | } 113 | 114 | impl Pixel for u8 { 115 | fn from_f64(f: f64) -> Self { 116 | f.round() as u8 117 | } 118 | } 119 | impl Pixel for u16 { 120 | fn from_f64(f: f64) -> Self { 121 | f.round() as u16 122 | } 123 | } 124 | impl Pixel for u32 { 125 | fn from_f64(f: f64) -> Self { 126 | f.round() as u32 127 | } 128 | } 129 | impl Pixel for i8 { 130 | fn from_f64(f: f64) -> Self { 131 | f.round() as i8 132 | } 133 | } 134 | impl Pixel for i16 { 135 | fn from_f64(f: f64) -> Self { 136 | f.round() as i16 137 | } 138 | } 139 | impl Pixel for i32 { 140 | fn from_f64(f: f64) -> Self { 141 | f.round() as i32 142 | } 143 | } 144 | impl Pixel for f32 { 145 | fn from_f64(f: f64) -> Self { 146 | f as f32 147 | } 148 | } 149 | impl Pixel for f64 { 150 | fn from_f64(f: f64) -> Self { 151 | f 152 | } 153 | } 154 | 155 | /// Checks that the scale factor is a normal positive `f64`. 156 | /// 157 | /// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from 158 | /// anywhere other than winit, it's recommended to validate them using this function before passing them to winit; 159 | /// otherwise, you risk panics. 160 | #[inline] 161 | pub fn validate_scale_factor(scale_factor: f64) -> bool { 162 | scale_factor.is_sign_positive() && scale_factor.is_normal() 163 | } 164 | 165 | /// A position represented in logical pixels. 166 | /// 167 | /// The position is stored as floats, so please be careful. Casting floats to integers truncates the 168 | /// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>` 169 | /// implementation is provided which does the rounding for you. 170 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)] 171 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 172 | pub struct LogicalPosition

{ 173 | pub x: P, 174 | pub y: P, 175 | } 176 | 177 | impl

LogicalPosition

{ 178 | #[inline] 179 | pub const fn new(x: P, y: P) -> Self { 180 | LogicalPosition { x, y } 181 | } 182 | } 183 | 184 | impl LogicalPosition

{ 185 | #[inline] 186 | pub fn from_physical>, X: Pixel>( 187 | physical: T, 188 | scale_factor: f64, 189 | ) -> Self { 190 | physical.into().to_logical(scale_factor) 191 | } 192 | 193 | #[inline] 194 | pub fn to_physical(&self, scale_factor: f64) -> PhysicalPosition { 195 | assert!(validate_scale_factor(scale_factor)); 196 | let x = self.x.into() * scale_factor; 197 | let y = self.y.into() * scale_factor; 198 | PhysicalPosition::new(x, y).cast() 199 | } 200 | 201 | #[inline] 202 | pub fn cast(&self) -> LogicalPosition { 203 | LogicalPosition { 204 | x: self.x.cast(), 205 | y: self.y.cast(), 206 | } 207 | } 208 | } 209 | 210 | impl From<(X, X)> for LogicalPosition

{ 211 | fn from((x, y): (X, X)) -> LogicalPosition

{ 212 | LogicalPosition::new(x.cast(), y.cast()) 213 | } 214 | } 215 | 216 | impl From> for (X, X) { 217 | fn from(p: LogicalPosition

) -> (X, X) { 218 | (p.x.cast(), p.y.cast()) 219 | } 220 | } 221 | 222 | impl From<[X; 2]> for LogicalPosition

{ 223 | fn from([x, y]: [X; 2]) -> LogicalPosition

{ 224 | LogicalPosition::new(x.cast(), y.cast()) 225 | } 226 | } 227 | 228 | impl From> for [X; 2] { 229 | fn from(p: LogicalPosition

) -> [X; 2] { 230 | [p.x.cast(), p.y.cast()] 231 | } 232 | } 233 | 234 | #[cfg(feature = "mint")] 235 | impl From> for LogicalPosition

{ 236 | fn from(p: mint::Point2

) -> Self { 237 | Self::new(p.x, p.y) 238 | } 239 | } 240 | 241 | #[cfg(feature = "mint")] 242 | impl From> for mint::Point2

{ 243 | fn from(p: LogicalPosition

) -> Self { 244 | mint::Point2 { x: p.x, y: p.y } 245 | } 246 | } 247 | 248 | /// A position represented in physical pixels. 249 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)] 250 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 251 | pub struct PhysicalPosition

{ 252 | pub x: P, 253 | pub y: P, 254 | } 255 | 256 | impl

PhysicalPosition

{ 257 | #[inline] 258 | pub const fn new(x: P, y: P) -> Self { 259 | PhysicalPosition { x, y } 260 | } 261 | } 262 | 263 | impl PhysicalPosition

{ 264 | #[inline] 265 | pub fn from_logical>, X: Pixel>( 266 | logical: T, 267 | scale_factor: f64, 268 | ) -> Self { 269 | logical.into().to_physical(scale_factor) 270 | } 271 | 272 | #[inline] 273 | pub fn to_logical(&self, scale_factor: f64) -> LogicalPosition { 274 | assert!(validate_scale_factor(scale_factor)); 275 | let x = self.x.into() / scale_factor; 276 | let y = self.y.into() / scale_factor; 277 | LogicalPosition::new(x, y).cast() 278 | } 279 | 280 | #[inline] 281 | pub fn cast(&self) -> PhysicalPosition { 282 | PhysicalPosition { 283 | x: self.x.cast(), 284 | y: self.y.cast(), 285 | } 286 | } 287 | } 288 | 289 | impl From<(X, X)> for PhysicalPosition

{ 290 | fn from((x, y): (X, X)) -> PhysicalPosition

{ 291 | PhysicalPosition::new(x.cast(), y.cast()) 292 | } 293 | } 294 | 295 | impl From> for (X, X) { 296 | fn from(p: PhysicalPosition

) -> (X, X) { 297 | (p.x.cast(), p.y.cast()) 298 | } 299 | } 300 | 301 | impl From<[X; 2]> for PhysicalPosition

{ 302 | fn from([x, y]: [X; 2]) -> PhysicalPosition

{ 303 | PhysicalPosition::new(x.cast(), y.cast()) 304 | } 305 | } 306 | 307 | impl From> for [X; 2] { 308 | fn from(p: PhysicalPosition

) -> [X; 2] { 309 | [p.x.cast(), p.y.cast()] 310 | } 311 | } 312 | 313 | #[cfg(feature = "mint")] 314 | impl From> for PhysicalPosition

{ 315 | fn from(p: mint::Point2

) -> Self { 316 | Self::new(p.x, p.y) 317 | } 318 | } 319 | 320 | #[cfg(feature = "mint")] 321 | impl From> for mint::Point2

{ 322 | fn from(p: PhysicalPosition

) -> Self { 323 | mint::Point2 { x: p.x, y: p.y } 324 | } 325 | } 326 | 327 | /// A size represented in logical pixels. 328 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)] 329 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 330 | pub struct LogicalSize

{ 331 | pub width: P, 332 | pub height: P, 333 | } 334 | 335 | impl

LogicalSize

{ 336 | #[inline] 337 | pub const fn new(width: P, height: P) -> Self { 338 | LogicalSize { width, height } 339 | } 340 | } 341 | 342 | impl LogicalSize

{ 343 | #[inline] 344 | pub fn from_physical>, X: Pixel>( 345 | physical: T, 346 | scale_factor: f64, 347 | ) -> Self { 348 | physical.into().to_logical(scale_factor) 349 | } 350 | 351 | #[inline] 352 | pub fn to_physical(&self, scale_factor: f64) -> PhysicalSize { 353 | assert!(validate_scale_factor(scale_factor)); 354 | let width = self.width.into() * scale_factor; 355 | let height = self.height.into() * scale_factor; 356 | PhysicalSize::new(width, height).cast() 357 | } 358 | 359 | #[inline] 360 | pub fn cast(&self) -> LogicalSize { 361 | LogicalSize { 362 | width: self.width.cast(), 363 | height: self.height.cast(), 364 | } 365 | } 366 | } 367 | 368 | impl From<(X, X)> for LogicalSize

{ 369 | fn from((x, y): (X, X)) -> LogicalSize

{ 370 | LogicalSize::new(x.cast(), y.cast()) 371 | } 372 | } 373 | 374 | impl From> for (X, X) { 375 | fn from(s: LogicalSize

) -> (X, X) { 376 | (s.width.cast(), s.height.cast()) 377 | } 378 | } 379 | 380 | impl From<[X; 2]> for LogicalSize

{ 381 | fn from([x, y]: [X; 2]) -> LogicalSize

{ 382 | LogicalSize::new(x.cast(), y.cast()) 383 | } 384 | } 385 | 386 | impl From> for [X; 2] { 387 | fn from(s: LogicalSize

) -> [X; 2] { 388 | [s.width.cast(), s.height.cast()] 389 | } 390 | } 391 | 392 | #[cfg(feature = "mint")] 393 | impl From> for LogicalSize

{ 394 | fn from(v: mint::Vector2

) -> Self { 395 | Self::new(v.x, v.y) 396 | } 397 | } 398 | 399 | #[cfg(feature = "mint")] 400 | impl From> for mint::Vector2

{ 401 | fn from(s: LogicalSize

) -> Self { 402 | mint::Vector2 { 403 | x: s.width, 404 | y: s.height, 405 | } 406 | } 407 | } 408 | 409 | /// A size represented in physical pixels. 410 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)] 411 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 412 | pub struct PhysicalSize

{ 413 | pub width: P, 414 | pub height: P, 415 | } 416 | 417 | impl

PhysicalSize

{ 418 | #[inline] 419 | pub const fn new(width: P, height: P) -> Self { 420 | PhysicalSize { width, height } 421 | } 422 | } 423 | 424 | impl PhysicalSize

{ 425 | #[inline] 426 | pub fn from_logical>, X: Pixel>(logical: T, scale_factor: f64) -> Self { 427 | logical.into().to_physical(scale_factor) 428 | } 429 | 430 | #[inline] 431 | pub fn to_logical(&self, scale_factor: f64) -> LogicalSize { 432 | assert!(validate_scale_factor(scale_factor)); 433 | let width = self.width.into() / scale_factor; 434 | let height = self.height.into() / scale_factor; 435 | LogicalSize::new(width, height).cast() 436 | } 437 | 438 | #[inline] 439 | pub fn cast(&self) -> PhysicalSize { 440 | PhysicalSize { 441 | width: self.width.cast(), 442 | height: self.height.cast(), 443 | } 444 | } 445 | } 446 | 447 | impl From<(X, X)> for PhysicalSize

{ 448 | fn from((x, y): (X, X)) -> PhysicalSize

{ 449 | PhysicalSize::new(x.cast(), y.cast()) 450 | } 451 | } 452 | 453 | impl From> for (X, X) { 454 | fn from(s: PhysicalSize

) -> (X, X) { 455 | (s.width.cast(), s.height.cast()) 456 | } 457 | } 458 | 459 | impl From<[X; 2]> for PhysicalSize

{ 460 | fn from([x, y]: [X; 2]) -> PhysicalSize

{ 461 | PhysicalSize::new(x.cast(), y.cast()) 462 | } 463 | } 464 | 465 | impl From> for [X; 2] { 466 | fn from(s: PhysicalSize

) -> [X; 2] { 467 | [s.width.cast(), s.height.cast()] 468 | } 469 | } 470 | 471 | #[cfg(feature = "mint")] 472 | impl From> for PhysicalSize

{ 473 | fn from(v: mint::Vector2

) -> Self { 474 | Self::new(v.x, v.y) 475 | } 476 | } 477 | 478 | #[cfg(feature = "mint")] 479 | impl From> for mint::Vector2

{ 480 | fn from(s: PhysicalSize

) -> Self { 481 | mint::Vector2 { 482 | x: s.width, 483 | y: s.height, 484 | } 485 | } 486 | } 487 | 488 | /// A size that's either physical or logical. 489 | #[derive(Debug, Copy, Clone, PartialEq)] 490 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 491 | pub enum Size { 492 | Physical(PhysicalSize), 493 | Logical(LogicalSize), 494 | } 495 | 496 | impl Size { 497 | pub fn new>(size: S) -> Size { 498 | size.into() 499 | } 500 | 501 | pub fn to_logical(&self, scale_factor: f64) -> LogicalSize

{ 502 | match *self { 503 | Size::Physical(size) => size.to_logical(scale_factor), 504 | Size::Logical(size) => size.cast(), 505 | } 506 | } 507 | 508 | pub fn to_physical(&self, scale_factor: f64) -> PhysicalSize

{ 509 | match *self { 510 | Size::Physical(size) => size.cast(), 511 | Size::Logical(size) => size.to_physical(scale_factor), 512 | } 513 | } 514 | 515 | pub fn clamp>(input: S, min: S, max: S, scale_factor: f64) -> Size { 516 | let (input, min, max) = ( 517 | input.into().to_physical::(scale_factor), 518 | min.into().to_physical::(scale_factor), 519 | max.into().to_physical::(scale_factor), 520 | ); 521 | 522 | let clamp = |input: f64, min: f64, max: f64| { 523 | if input < min { 524 | min 525 | } else if input > max { 526 | max 527 | } else { 528 | input 529 | } 530 | }; 531 | 532 | let width = clamp(input.width, min.width, max.width); 533 | let height = clamp(input.height, min.height, max.height); 534 | 535 | PhysicalSize::new(width, height).into() 536 | } 537 | } 538 | 539 | impl From> for Size { 540 | #[inline] 541 | fn from(size: PhysicalSize

) -> Size { 542 | Size::Physical(size.cast()) 543 | } 544 | } 545 | 546 | impl From> for Size { 547 | #[inline] 548 | fn from(size: LogicalSize

) -> Size { 549 | Size::Logical(size.cast()) 550 | } 551 | } 552 | 553 | /// A position that's either physical or logical. 554 | #[derive(Debug, Copy, Clone, PartialEq)] 555 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 556 | pub enum Position { 557 | Physical(PhysicalPosition), 558 | Logical(LogicalPosition), 559 | } 560 | 561 | impl Position { 562 | pub fn new>(position: S) -> Position { 563 | position.into() 564 | } 565 | 566 | pub fn to_logical(&self, scale_factor: f64) -> LogicalPosition

{ 567 | match *self { 568 | Position::Physical(position) => position.to_logical(scale_factor), 569 | Position::Logical(position) => position.cast(), 570 | } 571 | } 572 | 573 | pub fn to_physical(&self, scale_factor: f64) -> PhysicalPosition

{ 574 | match *self { 575 | Position::Physical(position) => position.cast(), 576 | Position::Logical(position) => position.to_physical(scale_factor), 577 | } 578 | } 579 | } 580 | 581 | impl From> for Position { 582 | #[inline] 583 | fn from(position: PhysicalPosition

) -> Position { 584 | Position::Physical(position.cast()) 585 | } 586 | } 587 | 588 | impl From> for Position { 589 | #[inline] 590 | fn from(position: LogicalPosition

) -> Position { 591 | Position::Logical(position.cast()) 592 | } 593 | } 594 | -------------------------------------------------------------------------------- /src/egl.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroU32; 2 | 3 | use glutin::{api::egl, config::ConfigSurfaceTypes, prelude::GlDisplay, surface::WindowSurface}; 4 | use sctk::reexports::client::{protocol::wl_surface, Proxy}; 5 | 6 | /// helper for initializing egl after creation of the first layer surface / window 7 | pub fn init_egl( 8 | surface: &wl_surface::WlSurface, 9 | width: u32, 10 | height: u32, 11 | ) -> ( 12 | egl::display::Display, 13 | egl::context::NotCurrentContext, 14 | glutin::api::egl::config::Config, 15 | egl::surface::Surface, 16 | ) { 17 | let mut display_handle = raw_window_handle::WaylandDisplayHandle::empty(); 18 | display_handle.display = surface 19 | .backend() 20 | .upgrade() 21 | .expect("Connection has been closed") 22 | .display_ptr() as *mut _; 23 | let display_handle = raw_window_handle::RawDisplayHandle::Wayland(display_handle); 24 | let mut window_handle = raw_window_handle::WaylandWindowHandle::empty(); 25 | window_handle.surface = surface.id().as_ptr() as *mut _; 26 | let window_handle = raw_window_handle::RawWindowHandle::Wayland(window_handle); 27 | 28 | // Initialize the EGL Wayland platform 29 | // 30 | // SAFETY: The connection is valid. 31 | let display = unsafe { egl::display::Display::new(display_handle) } 32 | .expect("Failed to initialize Wayland EGL platform"); 33 | 34 | // Find a suitable config for the window. 35 | let config_template = glutin::config::ConfigTemplateBuilder::default() 36 | .compatible_with_native_window(window_handle) 37 | .with_surface_type(ConfigSurfaceTypes::WINDOW) 38 | .with_api(glutin::config::Api::GLES2) 39 | .build(); 40 | let config = unsafe { display.find_configs(config_template) } 41 | .unwrap() 42 | .next() 43 | .expect("No available configs"); 44 | let gl_attrs = glutin::context::ContextAttributesBuilder::default() 45 | .with_context_api(glutin::context::ContextApi::OpenGl(None)) 46 | .build(Some(window_handle)); 47 | let gles_attrs = glutin::context::ContextAttributesBuilder::default() 48 | .with_context_api(glutin::context::ContextApi::Gles(None)) 49 | .build(Some(window_handle)); 50 | 51 | let context = unsafe { display.create_context(&config, &gl_attrs) } 52 | .or_else(|_| unsafe { display.create_context(&config, &gles_attrs) }) 53 | .expect("Failed to create context"); 54 | 55 | let surface_attrs = glutin::surface::SurfaceAttributesBuilder::::default() 56 | .build( 57 | window_handle, 58 | NonZeroU32::new(width).unwrap(), 59 | NonZeroU32::new(height).unwrap(), 60 | ); 61 | let surface = unsafe { display.create_window_surface(&config, &surface_attrs) } 62 | .expect("Failed to create surface"); 63 | 64 | (display, context, config, surface) 65 | } 66 | 67 | pub fn get_surface( 68 | display: &egl::display::Display, 69 | config: &glutin::api::egl::config::Config, 70 | surface: &wl_surface::WlSurface, 71 | width: u32, 72 | height: u32, 73 | ) -> egl::surface::Surface { 74 | let mut window_handle = raw_window_handle::WaylandWindowHandle::empty(); 75 | window_handle.surface = surface.id().as_ptr() as *mut _; 76 | let window_handle = raw_window_handle::RawWindowHandle::Wayland(window_handle); 77 | let surface_attrs = glutin::surface::SurfaceAttributesBuilder::::default() 78 | .build( 79 | window_handle, 80 | NonZeroU32::new(width).unwrap(), 81 | NonZeroU32::new(height).unwrap(), 82 | ); 83 | let surface = unsafe { display.create_window_surface(&config, &surface_attrs) } 84 | .expect("Failed to create surface"); 85 | surface 86 | } 87 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use iced_futures::futures; 2 | 3 | /// An error that occurred while running an application. 4 | #[derive(Debug, thiserror::Error)] 5 | pub enum Error { 6 | /// The futures executor could not be created. 7 | #[error("the futures executor could not be created")] 8 | ExecutorCreationFailed(futures::io::Error), 9 | 10 | /// The application window could not be created. 11 | #[error("the application window could not be created")] 12 | WindowCreationFailed(Box), 13 | 14 | /// The application graphics context could not be created. 15 | #[error("the application graphics context could not be created")] 16 | GraphicsCreationFailed(iced_graphics::Error), 17 | } 18 | 19 | impl From for Error { 20 | fn from(error: iced_graphics::Error) -> Error { 21 | Error::GraphicsCreationFailed(error) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/event_loop/control_flow.rs: -------------------------------------------------------------------------------- 1 | /// Set by the user callback given to the [`EventLoop::run`] method. 2 | /// 3 | /// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`] is emitted. 4 | /// 5 | /// Defaults to [`Poll`]. 6 | /// 7 | /// ## Persistency 8 | /// 9 | /// Almost every change is persistent between multiple calls to the event loop closure within a 10 | /// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset. 11 | /// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will 12 | /// reset the control flow to [`Poll`]. 13 | /// 14 | /// [`ExitWithCode`]: Self::ExitWithCode 15 | /// [`Poll`]: Self::Poll 16 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 17 | pub enum ControlFlow { 18 | /// When the current loop iteration finishes, immediately begin a new iteration regardless of 19 | /// whether or not new events are available to process. 20 | /// 21 | /// ## Platform-specific 22 | /// 23 | /// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes 24 | /// the events in the queue may be sent before the next `requestAnimationFrame` callback, for 25 | /// example when the scaling of the page has changed. This should be treated as an implementation 26 | /// detail which should not be relied on. 27 | Poll, 28 | /// When the current loop iteration finishes, suspend the thread until another event arrives. 29 | Wait, 30 | /// When the current loop iteration finishes, suspend the thread until either another event 31 | /// arrives or the given time is reached. 32 | /// 33 | /// Useful for implementing efficient timers. Applications which want to render at the display's 34 | /// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API 35 | /// to reduce odds of missed frames. 36 | /// 37 | /// [`Poll`]: Self::Poll 38 | WaitUntil(std::time::Instant), 39 | /// Send a [`LoopDestroyed`] event and stop the event loop. This variant is *sticky* - once set, 40 | /// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will 41 | /// result in the `control_flow` parameter being reset to `ExitWithCode`. 42 | /// 43 | /// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this 44 | /// with exit code 0. 45 | /// 46 | /// ## Platform-specific 47 | /// 48 | /// - **Android / iOS / WASM:** The supplied exit code is unused. 49 | /// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used, 50 | /// which can cause surprises with negative exit values (`-42` would end up as `214`). See 51 | /// [`std::process::exit`]. 52 | /// 53 | /// [`LoopDestroyed`]: Event::LoopDestroyed 54 | /// [`Exit`]: ControlFlow::Exit 55 | ExitWithCode(i32), 56 | } 57 | -------------------------------------------------------------------------------- /src/event_loop/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod control_flow; 2 | pub mod proxy; 3 | pub mod state; 4 | 5 | use std::{ 6 | collections::HashMap, 7 | fmt::Debug, 8 | mem, 9 | time::{Duration, Instant}, 10 | }; 11 | 12 | use crate::{ 13 | application::Event, 14 | sctk_event::{ 15 | IcedSctkEvent, LayerSurfaceEventVariant, PopupEventVariant, SctkEvent, StartCause, 16 | SurfaceUserRequest, WindowEventVariant, 17 | }, 18 | settings, 19 | }; 20 | 21 | use iced_native::command::platform_specific::{ 22 | self, 23 | wayland::{layer_surface::SctkLayerSurfaceSettings, window::SctkWindowSettings}, 24 | }; 25 | use sctk::{ 26 | compositor::CompositorState, 27 | event_loop::WaylandSource, 28 | output::OutputState, 29 | reexports::{ 30 | calloop::{self, EventLoop}, 31 | client::{ 32 | backend::ObjectId, globals::registry_queue_init, protocol::wl_surface::WlSurface, 33 | ConnectError, Connection, DispatchError, Proxy, 34 | }, 35 | }, 36 | registry::RegistryState, 37 | seat::SeatState, 38 | shell::{ 39 | layer::LayerShell, 40 | xdg::{window::XdgWindowState, XdgShellState}, 41 | }, 42 | shm::ShmState, 43 | }; 44 | use wayland_backend::client::WaylandError; 45 | 46 | use self::{ 47 | control_flow::ControlFlow, 48 | state::{LayerSurfaceCreationError, SctkState}, 49 | }; 50 | 51 | // impl SctkSurface { 52 | // pub fn hash(&self) -> u64 { 53 | // let hasher = DefaultHasher::new(); 54 | // match self { 55 | // SctkSurface::LayerSurface(s) => s.wl_surface().id().hash(.hash(&mut hasher)), 56 | // SctkSurface::Window(s) => s.wl_surface().id().hash(.hash(&mut hasher)), 57 | // SctkSurface::Popup(s) => s.wl_surface().id().hash(.hash(&mut hasher)), 58 | // }; 59 | // hasher.finish() 60 | // } 61 | // } 62 | 63 | #[derive(Debug, Default, Clone, Copy)] 64 | pub struct Features { 65 | // TODO 66 | } 67 | 68 | #[derive(Debug)] 69 | pub struct SctkEventLoop { 70 | // TODO after merged 71 | // pub data_device_manager_state: DataDeviceManagerState, 72 | pub(crate) event_loop: EventLoop<'static, SctkState>, 73 | pub(crate) wayland_dispatcher: 74 | calloop::Dispatcher<'static, WaylandSource>, SctkState>, 75 | pub(crate) features: Features, 76 | /// A proxy to wake up event loop. 77 | pub event_loop_awakener: calloop::ping::Ping, 78 | /// A sender for submitting user events in the event loop 79 | pub user_events_sender: calloop::channel::Sender>, 80 | pub(crate) state: SctkState, 81 | } 82 | 83 | impl SctkEventLoop 84 | where 85 | T: 'static + Debug, 86 | { 87 | pub(crate) fn new(_settings: &settings::Settings) -> Result { 88 | let connection = Connection::connect_to_env()?; 89 | let _display = connection.display(); 90 | let (globals, event_queue) = registry_queue_init(&connection).unwrap(); 91 | let event_loop = calloop::EventLoop::>::try_new().unwrap(); 92 | let loop_handle = event_loop.handle(); 93 | 94 | let qh = event_queue.handle(); 95 | let registry_state = RegistryState::new(&globals); 96 | 97 | let (ping, ping_source) = calloop::ping::make_ping().unwrap(); 98 | // TODO 99 | loop_handle 100 | .insert_source(ping_source, |_, _, _state| { 101 | // Drain events here as well to account for application doing batch event processing 102 | // on RedrawEventsCleared. 103 | // shim::handle_window_requests(state); 104 | todo!() 105 | }) 106 | .unwrap(); 107 | let (user_events_sender, user_events_channel) = calloop::channel::channel(); 108 | 109 | loop_handle 110 | .insert_source(user_events_channel, |event, _, state| match event { 111 | calloop::channel::Event::Msg(e) => { 112 | state.pending_user_events.push(e); 113 | } 114 | calloop::channel::Event::Closed => {} 115 | }) 116 | .unwrap(); 117 | let wayland_source = WaylandSource::new(event_queue).unwrap(); 118 | 119 | let wayland_dispatcher = 120 | calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| { 121 | queue.dispatch_pending(winit_state) 122 | }); 123 | 124 | let _wayland_source_dispatcher = event_loop 125 | .handle() 126 | .register_dispatcher(wayland_dispatcher.clone()) 127 | .unwrap(); 128 | 129 | Ok(Self { 130 | event_loop, 131 | wayland_dispatcher, 132 | state: SctkState { 133 | connection, 134 | registry_state, 135 | seat_state: SeatState::new(&globals, &qh), 136 | output_state: OutputState::new(&globals, &qh), 137 | compositor_state: CompositorState::bind(&globals, &qh) 138 | .expect("wl_compositor is not available"), 139 | shm_state: ShmState::bind(&globals, &qh).expect("wl_shm is not available"), 140 | xdg_shell_state: XdgShellState::bind(&globals, &qh) 141 | .expect("xdg shell is not available"), 142 | xdg_window_state: XdgWindowState::bind(&globals, &qh), 143 | layer_shell: LayerShell::bind(&globals, &qh).ok(), 144 | 145 | // data_device_manager_state: DataDeviceManagerState::new(), 146 | queue_handle: qh, 147 | loop_handle: loop_handle, 148 | 149 | cursor_surface: None, 150 | multipool: None, 151 | outputs: Vec::new(), 152 | seats: Vec::new(), 153 | windows: Vec::new(), 154 | layer_surfaces: Vec::new(), 155 | popups: Vec::new(), 156 | kbd_focus: None, 157 | window_user_requests: HashMap::new(), 158 | window_compositor_updates: HashMap::new(), 159 | sctk_events: Vec::new(), 160 | popup_compositor_updates: Default::default(), 161 | layer_surface_compositor_updates: Default::default(), 162 | layer_surface_user_requests: Default::default(), 163 | popup_user_requests: Default::default(), 164 | pending_user_events: Vec::new(), 165 | }, 166 | features: Default::default(), 167 | event_loop_awakener: ping, 168 | user_events_sender, 169 | }) 170 | } 171 | 172 | pub fn proxy(&self) -> proxy::Proxy> { 173 | proxy::Proxy::new(self.user_events_sender.clone()) 174 | } 175 | 176 | pub fn get_layer_surface( 177 | &mut self, 178 | layer_surface: SctkLayerSurfaceSettings, 179 | ) -> Result<(iced_native::window::Id, WlSurface), LayerSurfaceCreationError> { 180 | self.state.get_layer_surface(layer_surface) 181 | } 182 | 183 | pub fn get_window( 184 | &mut self, 185 | settings: SctkWindowSettings, 186 | ) -> (iced_native::window::Id, WlSurface) { 187 | self.state.get_window(settings) 188 | } 189 | 190 | pub fn run_return(&mut self, mut callback: F) -> i32 191 | where 192 | F: FnMut(IcedSctkEvent, &SctkState, &mut ControlFlow), 193 | { 194 | let mut control_flow = ControlFlow::Poll; 195 | 196 | callback( 197 | IcedSctkEvent::NewEvents(StartCause::Init), 198 | &self.state, 199 | &mut control_flow, 200 | ); 201 | 202 | let mut surface_user_requests: Vec<(ObjectId, SurfaceUserRequest)> = Vec::new(); 203 | 204 | let mut event_sink_back_buffer = Vec::new(); 205 | 206 | // NOTE We break on errors from dispatches, since if we've got protocol error 207 | // libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not 208 | // really an option. Instead we inform that the event loop got destroyed. We may 209 | // communicate an error that something was terminated, but winit doesn't provide us 210 | // with an API to do that via some event. 211 | // Still, we set the exit code to the error's OS error code, or to 1 if not possible. 212 | let exit_code = loop { 213 | // Send pending events to the server. 214 | match self.state.connection.flush() { 215 | Ok(_) => {} 216 | Err(error) => { 217 | break match error { 218 | WaylandError::Io(err) => err.raw_os_error(), 219 | WaylandError::Protocol(_) => None, 220 | } 221 | .unwrap_or(1) 222 | } 223 | } 224 | 225 | // During the run of the user callback, some other code monitoring and reading the 226 | // Wayland socket may have been run (mesa for example does this with vsync), if that 227 | // is the case, some events may have been enqueued in our event queue. 228 | // 229 | // If some messages are there, the event loop needs to behave as if it was instantly 230 | // woken up by messages arriving from the Wayland socket, to avoid delaying the 231 | // dispatch of these events until we're woken up again. 232 | let instant_wakeup = { 233 | let mut wayland_source = self.wayland_dispatcher.as_source_mut(); 234 | let queue = wayland_source.queue(); 235 | match queue.dispatch_pending(&mut self.state) { 236 | Ok(dispatched) => dispatched > 0, 237 | // TODO better error handling 238 | Err(error) => { 239 | break match error { 240 | DispatchError::BadMessage { .. } => None, 241 | DispatchError::Backend(err) => match err { 242 | WaylandError::Io(err) => err.raw_os_error(), 243 | WaylandError::Protocol(_) => None, 244 | }, 245 | } 246 | .unwrap_or(1) 247 | } 248 | } 249 | }; 250 | 251 | match control_flow { 252 | ControlFlow::ExitWithCode(code) => break code, 253 | ControlFlow::Poll => { 254 | // Non-blocking dispatch. 255 | let timeout = Duration::from_millis(0); 256 | if let Err(error) = self.event_loop.dispatch(Some(timeout), &mut self.state) { 257 | break raw_os_err(error); 258 | } 259 | 260 | callback( 261 | IcedSctkEvent::NewEvents(StartCause::Poll), 262 | &self.state, 263 | &mut control_flow, 264 | ); 265 | } 266 | ControlFlow::Wait => { 267 | let timeout = if instant_wakeup { 268 | Some(Duration::from_millis(0)) 269 | } else { 270 | None 271 | }; 272 | 273 | if let Err(error) = self.event_loop.dispatch(timeout, &mut self.state) { 274 | break raw_os_err(error); 275 | } 276 | 277 | callback( 278 | IcedSctkEvent::NewEvents(StartCause::WaitCancelled { 279 | start: Instant::now(), 280 | requested_resume: None, 281 | }), 282 | &self.state, 283 | &mut control_flow, 284 | ); 285 | } 286 | ControlFlow::WaitUntil(deadline) => { 287 | let start = Instant::now(); 288 | 289 | // Compute the amount of time we'll block for. 290 | let duration = if deadline > start && !instant_wakeup { 291 | deadline - start 292 | } else { 293 | Duration::from_millis(0) 294 | }; 295 | 296 | if let Err(error) = self.event_loop.dispatch(Some(duration), &mut self.state) { 297 | break raw_os_err(error); 298 | } 299 | 300 | let now = Instant::now(); 301 | 302 | if now < deadline { 303 | callback( 304 | IcedSctkEvent::NewEvents(StartCause::WaitCancelled { 305 | start, 306 | requested_resume: Some(deadline), 307 | }), 308 | &self.state, 309 | &mut control_flow, 310 | ) 311 | } else { 312 | callback( 313 | IcedSctkEvent::NewEvents(StartCause::ResumeTimeReached { 314 | start, 315 | requested_resume: deadline, 316 | }), 317 | &self.state, 318 | &mut control_flow, 319 | ) 320 | } 321 | } 322 | } 323 | 324 | // The purpose of the back buffer and that swap is to not hold borrow_mut when 325 | // we're doing callback to the user, since we can double borrow if the user decides 326 | // to create a window in one of those callbacks. 327 | std::mem::swap(&mut event_sink_back_buffer, &mut self.state.sctk_events); 328 | 329 | // Handle pending sctk events. 330 | let mut must_redraw = Vec::new(); 331 | 332 | for event in event_sink_back_buffer.drain(..) { 333 | match event { 334 | SctkEvent::Draw(id) => must_redraw.push(id), 335 | SctkEvent::PopupEvent { 336 | variant: PopupEventVariant::Done, 337 | toplevel_id, 338 | parent_id, 339 | id, 340 | } => { 341 | match self 342 | .state 343 | .popups 344 | .iter() 345 | .position(|s| s.popup.wl_surface().id() == id.id()) 346 | { 347 | Some(p) => { 348 | let _p = self.state.popups.remove(p); 349 | sticky_exit_callback( 350 | IcedSctkEvent::SctkEvent(SctkEvent::PopupEvent { 351 | variant: PopupEventVariant::Done, 352 | toplevel_id, 353 | parent_id, 354 | id, 355 | }), 356 | &self.state, 357 | &mut control_flow, 358 | &mut callback, 359 | ); 360 | } 361 | None => continue, 362 | }; 363 | } 364 | SctkEvent::LayerSurfaceEvent { 365 | variant: LayerSurfaceEventVariant::Done, 366 | id, 367 | } => { 368 | if let Some(i) = self 369 | .state 370 | .layer_surfaces 371 | .iter() 372 | .position(|l| l.surface.wl_surface().id() == id.id()) 373 | { 374 | let _l = self.state.layer_surfaces.remove(i); 375 | sticky_exit_callback( 376 | IcedSctkEvent::SctkEvent(SctkEvent::LayerSurfaceEvent { 377 | variant: LayerSurfaceEventVariant::Done, 378 | id, 379 | }), 380 | &self.state, 381 | &mut control_flow, 382 | &mut callback, 383 | ); 384 | } 385 | } 386 | SctkEvent::WindowEvent { 387 | variant: WindowEventVariant::Close, 388 | id, 389 | } => { 390 | if let Some(i) = self 391 | .state 392 | .layer_surfaces 393 | .iter() 394 | .position(|l| l.surface.wl_surface().id() == id.id()) 395 | { 396 | let w = self.state.windows.remove(i); 397 | w.window.xdg_toplevel().destroy(); 398 | sticky_exit_callback( 399 | IcedSctkEvent::SctkEvent(SctkEvent::WindowEvent { 400 | variant: WindowEventVariant::Close, 401 | id, 402 | }), 403 | &self.state, 404 | &mut control_flow, 405 | &mut callback, 406 | ); 407 | } 408 | } 409 | _ => sticky_exit_callback( 410 | IcedSctkEvent::SctkEvent(event), 411 | &self.state, 412 | &mut control_flow, 413 | &mut callback, 414 | ), 415 | } 416 | } 417 | 418 | // handle events indirectly via callback to the user. 419 | let (sctk_events, user_events): (Vec<_>, Vec<_>) = self 420 | .state 421 | .pending_user_events 422 | .drain(..) 423 | .partition(|e| matches!(e, Event::SctkEvent(_))); 424 | let mut to_commit = HashMap::new(); 425 | for event in sctk_events.into_iter().chain(user_events.into_iter()) { 426 | match event { 427 | Event::SctkEvent(event) => { 428 | sticky_exit_callback(event, &self.state, &mut control_flow, &mut callback) 429 | } 430 | Event::LayerSurface(action) => match action { 431 | platform_specific::wayland::layer_surface::Action::LayerSurface { 432 | builder, 433 | _phantom, 434 | } => { 435 | // TODO ASHLEY: error handling 436 | if let Ok((id, wl_surface)) = self.state.get_layer_surface(builder) { 437 | let object_id = wl_surface.id(); 438 | sticky_exit_callback( 439 | IcedSctkEvent::SctkEvent(SctkEvent::LayerSurfaceEvent { 440 | variant: LayerSurfaceEventVariant::Created(object_id.clone(), id), 441 | id: wl_surface.clone(), 442 | }), 443 | &self.state, 444 | &mut control_flow, 445 | &mut callback, 446 | ); 447 | } 448 | } 449 | platform_specific::wayland::layer_surface::Action::Size { 450 | id, 451 | width, 452 | height, 453 | } => { 454 | if let Some(layer_surface) = self.state.layer_surfaces.iter_mut().find(|l| l.id == id) { 455 | layer_surface.requested_size = (width, height); 456 | layer_surface.surface.set_size(width.unwrap_or_default(), height.unwrap_or_default()); 457 | to_commit.insert(id, layer_surface.surface.wl_surface().clone()); 458 | } 459 | }, 460 | platform_specific::wayland::layer_surface::Action::Destroy(id) => { 461 | if let Some(i) = self.state.layer_surfaces.iter().position(|l| &l.id == &id) { 462 | let l = self.state.layer_surfaces.remove(i); 463 | sticky_exit_callback( 464 | IcedSctkEvent::SctkEvent(SctkEvent::LayerSurfaceEvent { 465 | variant: LayerSurfaceEventVariant::Done, 466 | id: l.surface.wl_surface().clone(), 467 | }), 468 | &self.state, 469 | &mut control_flow, 470 | &mut callback, 471 | ); 472 | } 473 | }, 474 | platform_specific::wayland::layer_surface::Action::Anchor { id, anchor } => { 475 | if let Some(layer_surface) = self.state.layer_surfaces.iter_mut().find(|l| l.id == id) { 476 | layer_surface.anchor = anchor; 477 | layer_surface.surface.set_anchor(anchor); 478 | to_commit.insert(id, layer_surface.surface.wl_surface().clone()); 479 | 480 | } 481 | } 482 | platform_specific::wayland::layer_surface::Action::ExclusiveZone { 483 | id, 484 | exclusive_zone, 485 | } => { 486 | if let Some(layer_surface) = self.state.layer_surfaces.iter_mut().find(|l| l.id == id) { 487 | layer_surface.exclusive_zone = exclusive_zone; 488 | layer_surface.surface.set_exclusive_zone(exclusive_zone); 489 | to_commit.insert(id, layer_surface.surface.wl_surface().clone()); 490 | } 491 | }, 492 | platform_specific::wayland::layer_surface::Action::Margin { 493 | id, 494 | margin, 495 | } => { 496 | if let Some(layer_surface) = self.state.layer_surfaces.iter_mut().find(|l| l.id == id) { 497 | layer_surface.margin = margin; 498 | layer_surface.surface.set_margin(margin.top, margin.right, margin.bottom, margin.left); 499 | to_commit.insert(id, layer_surface.surface.wl_surface().clone()); 500 | } 501 | }, 502 | platform_specific::wayland::layer_surface::Action::KeyboardInteractivity { id, keyboard_interactivity } => { 503 | if let Some(layer_surface) = self.state.layer_surfaces.iter_mut().find(|l| l.id == id) { 504 | layer_surface.keyboard_interactivity = keyboard_interactivity; 505 | layer_surface.surface.set_keyboard_interactivity(keyboard_interactivity); 506 | to_commit.insert(id, layer_surface.surface.wl_surface().clone()); 507 | 508 | } 509 | }, 510 | platform_specific::wayland::layer_surface::Action::Layer { id, layer } => { 511 | if let Some(layer_surface) = self.state.layer_surfaces.iter_mut().find(|l| l.id == id) { 512 | layer_surface.layer = layer; 513 | layer_surface.surface.set_layer(layer); 514 | to_commit.insert(id, layer_surface.surface.wl_surface().clone()); 515 | 516 | } 517 | }, 518 | }, 519 | Event::SetCursor(_) => { 520 | // TODO set cursor after cursor theming PR is merged 521 | // https://github.com/Smithay/client-toolkit/pull/306 522 | } 523 | Event::Window(action) => match action { 524 | platform_specific::wayland::window::Action::Window { builder, _phantom } => { 525 | let (id, wl_surface) = self.state.get_window(builder); 526 | let object_id = wl_surface.id(); 527 | sticky_exit_callback( 528 | IcedSctkEvent::SctkEvent(SctkEvent::WindowEvent { variant: WindowEventVariant::Created(object_id.clone(), id), id: wl_surface.clone() }), 529 | &self.state, 530 | &mut control_flow, 531 | &mut callback, 532 | ); 533 | }, 534 | platform_specific::wayland::window::Action::Size { id, width, height } => { 535 | if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { 536 | window.requested_size = Some((width, height)); 537 | window.window.xdg_surface().set_window_geometry(0, 0, width.max(1) as i32, height.max(1) as i32); 538 | to_commit.insert(id, window.window.wl_surface().clone()); 539 | // TODO Ashley maybe don't force window size? 540 | if let Some(mut prev_configure) = window.last_configure.clone() { 541 | prev_configure.new_size = Some((width, height)); 542 | sticky_exit_callback( 543 | IcedSctkEvent::SctkEvent(SctkEvent::WindowEvent { variant: WindowEventVariant::Configure(prev_configure, window.window.wl_surface().clone(), false), id: window.window.wl_surface().clone()}), 544 | &self.state, 545 | &mut control_flow, 546 | &mut callback, 547 | ); 548 | } 549 | } 550 | }, 551 | platform_specific::wayland::window::Action::MinSize { id, size } => { 552 | if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { 553 | window.window.set_min_size(size); 554 | to_commit.insert(id, window.window.wl_surface().clone()); 555 | } 556 | }, 557 | platform_specific::wayland::window::Action::MaxSize { id, size } => { 558 | if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { 559 | window.window.set_max_size(size); 560 | to_commit.insert(id, window.window.wl_surface().clone()); 561 | } 562 | }, 563 | platform_specific::wayland::window::Action::Title { id, title } => { 564 | if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { 565 | window.window.set_title(title); 566 | to_commit.insert(id, window.window.wl_surface().clone()); 567 | } 568 | }, 569 | platform_specific::wayland::window::Action::Minimize { id } => { 570 | if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { 571 | window.window.set_mimimized(); 572 | to_commit.insert(id, window.window.wl_surface().clone()); 573 | } 574 | }, 575 | platform_specific::wayland::window::Action::Maximize { id } => { 576 | if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { 577 | window.window.set_maximized(); 578 | to_commit.insert(id, window.window.wl_surface().clone()); 579 | } 580 | }, 581 | platform_specific::wayland::window::Action::UnsetMaximize { id } => { 582 | if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { 583 | window.window.unset_maximized(); 584 | to_commit.insert(id, window.window.wl_surface().clone()); 585 | } 586 | }, 587 | platform_specific::wayland::window::Action::Fullscreen { id } => { 588 | if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { 589 | // TODO ASHLEY: allow specific output to be requested for fullscreen? 590 | window.window.set_fullscreen(None); 591 | to_commit.insert(id, window.window.wl_surface().clone()); 592 | } 593 | }, 594 | platform_specific::wayland::window::Action::UnsetFullscreen { id } => { 595 | if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { 596 | window.window.unset_fullscreen(); 597 | to_commit.insert(id, window.window.wl_surface().clone()); 598 | } 599 | }, 600 | platform_specific::wayland::window::Action::InteractiveMove { id } => { 601 | if let Some(window) = self.state.windows.iter_mut().find(|w| w.id == id) { 602 | todo!(); 603 | } 604 | }, 605 | platform_specific::wayland::window::Action::InteractiveResize { id, edge } => todo!(), 606 | platform_specific::wayland::window::Action::ShowWindowMenu { id, x, y } => todo!(), 607 | platform_specific::wayland::window::Action::Destroy(id) => { 608 | if let Some(i) = self.state.windows.iter().position(|l| &l.id == &id) { 609 | let window = self.state.windows.remove(i); 610 | window.window.xdg_toplevel().destroy(); 611 | sticky_exit_callback( 612 | IcedSctkEvent::SctkEvent(SctkEvent::WindowEvent { 613 | variant: WindowEventVariant::Close, 614 | id: window.window.wl_surface().clone(), 615 | }), 616 | &self.state, 617 | &mut control_flow, 618 | &mut callback, 619 | ); 620 | } 621 | }, 622 | }, 623 | Event::Popup(action) => match action { 624 | platform_specific::wayland::popup::Action::Popup { popup, .. } => { 625 | if let Ok((id, parent_id, toplevel_id, wl_surface)) = self.state.get_popup(popup) { 626 | let object_id = wl_surface.id(); 627 | sticky_exit_callback( 628 | IcedSctkEvent::SctkEvent(SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Created(object_id.clone(), id), toplevel_id, parent_id, id: wl_surface.clone() }), 629 | &self.state, 630 | &mut control_flow, 631 | &mut callback, 632 | ); 633 | } 634 | }, 635 | // XXX popup destruction must be done carefully 636 | // first destroy the uppermost popup, then work down to the requested popup 637 | platform_specific::wayland::popup::Action::Destroy { id } => { 638 | let sctk_popup = match self.state 639 | .popups 640 | .iter() 641 | .position(|s| s.id == id) 642 | { 643 | Some(p) => self.state.popups.remove(p), 644 | None => continue, 645 | }; 646 | let mut to_destroy = vec![sctk_popup]; 647 | while let Some(popup_to_destroy) = to_destroy.last() { 648 | match popup_to_destroy.parent.clone() { 649 | state::SctkSurface::LayerSurface(_) | state::SctkSurface::Window(_) => { 650 | break; 651 | } 652 | state::SctkSurface::Popup(popup_to_destroy_first) => { 653 | let popup_to_destroy_first = self 654 | .state 655 | .popups 656 | .iter() 657 | .position(|p| p.popup.wl_surface() == &popup_to_destroy_first) 658 | .unwrap(); 659 | let popup_to_destroy_first = self.state.popups.remove(popup_to_destroy_first); 660 | to_destroy.push(popup_to_destroy_first); 661 | } 662 | } 663 | } 664 | for popup in to_destroy.into_iter().rev() { 665 | sticky_exit_callback(IcedSctkEvent::SctkEvent(SctkEvent::PopupEvent { 666 | variant: PopupEventVariant::Done, 667 | toplevel_id: popup.toplevel.clone(), 668 | parent_id: popup.parent.wl_surface().clone(), 669 | id: popup.popup.wl_surface().clone(), 670 | }), 671 | &self.state, 672 | &mut control_flow, 673 | &mut callback, 674 | ); 675 | } 676 | }, 677 | platform_specific::wayland::popup::Action::Reposition { id, positioner } => todo!(), 678 | platform_specific::wayland::popup::Action::Grab { id } => todo!(), 679 | }, 680 | } 681 | } 682 | 683 | // commit changes made via actions 684 | for s in to_commit { 685 | s.1.commit(); 686 | } 687 | 688 | // Send events cleared. 689 | sticky_exit_callback( 690 | IcedSctkEvent::MainEventsCleared, 691 | &self.state, 692 | &mut control_flow, 693 | &mut callback, 694 | ); 695 | 696 | // Apply user requests, so every event required resize and latter surface commit will 697 | // be applied right before drawing. This will also ensure that every `RedrawRequested` 698 | // event will be delivered in time. 699 | // Process 'new' pending updates from compositor. 700 | surface_user_requests.clear(); 701 | surface_user_requests.extend( 702 | self.state 703 | .window_user_requests 704 | .iter_mut() 705 | .map(|(wid, window_request)| (wid.clone(), mem::take(window_request))), 706 | ); 707 | 708 | // Handle RedrawRequested requests. 709 | for (surface_id, mut surface_request) in surface_user_requests.iter() { 710 | if let Some(i) = must_redraw.iter().position(|a_id| &a_id.id() == surface_id) { 711 | must_redraw.remove(i); 712 | } 713 | let wl_suface = self 714 | .state 715 | .windows 716 | .iter() 717 | .map(|w| w.window.wl_surface()) 718 | .chain( 719 | self.state 720 | .layer_surfaces 721 | .iter() 722 | .map(|l| l.surface.wl_surface()), 723 | ) 724 | .find(|s| s.id() == *surface_id) 725 | .unwrap(); 726 | 727 | // Handle refresh of the frame. 728 | if surface_request.refresh_frame { 729 | // In general refreshing the frame requires surface commit, those force user 730 | // to redraw. 731 | surface_request.redraw_requested = true; 732 | } 733 | 734 | // Handle redraw request. 735 | if surface_request.redraw_requested { 736 | sticky_exit_callback( 737 | IcedSctkEvent::RedrawRequested(surface_id.clone()), 738 | &self.state, 739 | &mut control_flow, 740 | &mut callback, 741 | ); 742 | } 743 | wl_suface.commit(); 744 | } 745 | 746 | for id in must_redraw { 747 | sticky_exit_callback( 748 | IcedSctkEvent::RedrawRequested(id.id()), 749 | &self.state, 750 | &mut control_flow, 751 | &mut callback, 752 | ); 753 | } 754 | 755 | // Send RedrawEventCleared. 756 | sticky_exit_callback( 757 | IcedSctkEvent::RedrawEventsCleared, 758 | &self.state, 759 | &mut control_flow, 760 | &mut callback, 761 | ); 762 | }; 763 | 764 | callback(IcedSctkEvent::LoopDestroyed, &self.state, &mut control_flow); 765 | exit_code 766 | } 767 | } 768 | 769 | fn sticky_exit_callback( 770 | evt: IcedSctkEvent, 771 | target: &SctkState, 772 | control_flow: &mut ControlFlow, 773 | callback: &mut F, 774 | ) where 775 | F: FnMut(IcedSctkEvent, &SctkState, &mut ControlFlow), 776 | { 777 | // make ControlFlow::ExitWithCode sticky by providing a dummy 778 | // control flow reference if it is already ExitWithCode. 779 | if let ControlFlow::ExitWithCode(code) = *control_flow { 780 | callback(evt, target, &mut ControlFlow::ExitWithCode(code)) 781 | } else { 782 | callback(evt, target, control_flow) 783 | } 784 | } 785 | 786 | fn raw_os_err(err: calloop::Error) -> i32 { 787 | match err { 788 | calloop::Error::IoError(err) => err.raw_os_error(), 789 | _ => None, 790 | } 791 | .unwrap_or(1) 792 | } 793 | -------------------------------------------------------------------------------- /src/event_loop/proxy.rs: -------------------------------------------------------------------------------- 1 | use iced_native::futures::{ 2 | channel::mpsc, 3 | task::{Context, Poll}, 4 | Sink, 5 | }; 6 | use sctk::reexports::calloop; 7 | use std::pin::Pin; 8 | 9 | /// An event loop proxy that implements `Sink`. 10 | #[derive(Debug)] 11 | pub struct Proxy { 12 | raw: calloop::channel::Sender, 13 | } 14 | 15 | impl Clone for Proxy { 16 | fn clone(&self) -> Self { 17 | Self { 18 | raw: self.raw.clone(), 19 | } 20 | } 21 | } 22 | 23 | impl Proxy { 24 | /// Creates a new [`Proxy`] from an `EventLoopProxy`. 25 | pub fn new(raw: calloop::channel::Sender) -> Self { 26 | Self { raw } 27 | } 28 | /// send an event 29 | pub fn send_event(&self, message: Message) { 30 | let _ = self.raw.send(message); 31 | } 32 | } 33 | 34 | impl Sink for Proxy { 35 | type Error = mpsc::SendError; 36 | 37 | fn poll_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 38 | Poll::Ready(Ok(())) 39 | } 40 | 41 | fn start_send(self: Pin<&mut Self>, message: Message) -> Result<(), Self::Error> { 42 | let _ = self.raw.send(message); 43 | 44 | Ok(()) 45 | } 46 | 47 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 48 | Poll::Ready(Ok(())) 49 | } 50 | 51 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 52 | Poll::Ready(Ok(())) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/event_loop/state.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fmt::Debug, sync::Arc}; 2 | 3 | use crate::{ 4 | application::Event, 5 | dpi::LogicalSize, 6 | sctk_event::{SctkEvent, SurfaceCompositorUpdate, SurfaceUserRequest}, 7 | }; 8 | 9 | use iced_native::{ 10 | command::platform_specific::{ 11 | self, 12 | wayland::{ 13 | layer_surface::{IcedMargin, SctkLayerSurfaceSettings}, 14 | popup::SctkPopupSettings, 15 | window::SctkWindowSettings, 16 | }, 17 | }, 18 | keyboard::Modifiers, 19 | window, 20 | }; 21 | use sctk::{ 22 | compositor::CompositorState, 23 | error::GlobalError, 24 | output::OutputState, 25 | reexports::{ 26 | calloop::LoopHandle, 27 | client::{ 28 | backend::ObjectId, 29 | protocol::{ 30 | wl_data_device::WlDataDevice, 31 | wl_keyboard::WlKeyboard, 32 | wl_output::WlOutput, 33 | wl_pointer::WlPointer, 34 | wl_seat::WlSeat, 35 | wl_surface::{self, WlSurface}, 36 | wl_touch::WlTouch, 37 | }, 38 | Connection, Proxy, QueueHandle, 39 | }, 40 | }, 41 | registry::RegistryState, 42 | seat::{keyboard::KeyEvent, SeatState}, 43 | shell::{ 44 | layer::{ 45 | Anchor, KeyboardInteractivity, Layer, LayerShell, LayerSurface, LayerSurfaceConfigure, 46 | }, 47 | xdg::{ 48 | popup::{Popup, PopupConfigure}, 49 | window::{Window, WindowConfigure, WindowDecorations, XdgWindowState}, 50 | XdgPositioner, XdgShellState, XdgShellSurface, 51 | }, 52 | }, 53 | shm::{multi::MultiPool, ShmState}, 54 | }; 55 | 56 | #[derive(Debug, Clone)] 57 | pub(crate) struct SctkSeat { 58 | pub(crate) seat: WlSeat, 59 | pub(crate) kbd: Option, 60 | pub(crate) kbd_focus: Option, 61 | pub(crate) last_kbd_press: Option, 62 | pub(crate) ptr: Option, 63 | pub(crate) ptr_focus: Option, 64 | pub(crate) last_ptr_press: Option<(u32, u32, u32)>, // (time, button, serial) 65 | pub(crate) touch: Option, 66 | pub(crate) data_device: Option, 67 | pub(crate) modifiers: Modifiers, 68 | } 69 | 70 | #[derive(Debug, Clone)] 71 | pub struct SctkWindow { 72 | pub(crate) id: iced_native::window::Id, 73 | pub(crate) window: Window, 74 | pub(crate) requested_size: Option<(u32, u32)>, 75 | pub(crate) current_size: Option<(u32, u32)>, 76 | pub(crate) last_configure: Option, 77 | /// Requests that SCTK window should perform. 78 | pub(crate) pending_requests: Vec>, 79 | } 80 | 81 | #[derive(Debug, Clone)] 82 | pub struct SctkLayerSurface { 83 | pub(crate) id: iced_native::window::Id, 84 | pub(crate) surface: LayerSurface, 85 | pub(crate) requested_size: (Option, Option), 86 | pub(crate) current_size: Option>, 87 | pub(crate) layer: Layer, 88 | pub(crate) anchor: Anchor, 89 | pub(crate) keyboard_interactivity: KeyboardInteractivity, 90 | pub(crate) margin: IcedMargin, 91 | pub(crate) exclusive_zone: i32, 92 | pub(crate) last_configure: Option, 93 | pub(crate) pending_requests: Vec>, 94 | } 95 | 96 | #[derive(Debug, Clone)] 97 | pub enum SctkSurface { 98 | LayerSurface(WlSurface), 99 | Window(WlSurface), 100 | Popup(WlSurface), 101 | } 102 | 103 | impl SctkSurface { 104 | pub fn wl_surface(&self) -> &WlSurface { 105 | match self { 106 | SctkSurface::LayerSurface(s) | SctkSurface::Window(s) | SctkSurface::Popup(s) => s, 107 | } 108 | } 109 | } 110 | 111 | #[derive(Debug, Clone)] 112 | pub struct SctkPopup { 113 | pub(crate) id: iced_native::window::Id, 114 | pub(crate) popup: Popup, 115 | pub(crate) parent: SctkSurface, 116 | pub(crate) toplevel: WlSurface, 117 | pub(crate) requested_size: (u32, u32), 118 | pub(crate) last_configure: Option, 119 | // pub(crate) positioner: XdgPositioner, 120 | pub(crate) pending_requests: Vec>, 121 | } 122 | 123 | /// Wrapper to carry sctk state. 124 | #[derive(Debug)] 125 | pub struct SctkState { 126 | // egl 127 | // pub(crate) context: Option, 128 | // pub(crate) glow: Option, 129 | // pub(crate) display: Option, 130 | // pub(crate) config: Option, 131 | /// the cursor wl_surface 132 | pub(crate) cursor_surface: Option, 133 | /// a memory pool 134 | pub(crate) multipool: Option>, 135 | 136 | // all present outputs 137 | pub(crate) outputs: Vec, 138 | // though (for now) only one seat will be active in an iced application at a time, all ought to be tracked 139 | // Active seat is the first seat in the list 140 | pub(crate) seats: Vec, 141 | // Windows / Surfaces 142 | /// Window list containing all SCTK windows. Since those windows aren't allowed 143 | /// to be sent to other threads, they live on the event loop's thread 144 | /// and requests from winit's windows are being forwarded to them either via 145 | /// `WindowUpdate` or buffer on the associated with it `WindowHandle`. 146 | pub(crate) windows: Vec>, 147 | pub(crate) layer_surfaces: Vec>, 148 | pub(crate) popups: Vec>, 149 | pub(crate) kbd_focus: Option, 150 | 151 | /// Window updates, which are coming from SCTK or the compositor, which require 152 | /// calling back to the sctk's downstream. They are handled right in the event loop, 153 | /// unlike the ones coming from buffers on the `WindowHandle`'s. 154 | pub popup_compositor_updates: HashMap, 155 | /// Window updates, which are coming from SCTK or the compositor, which require 156 | /// calling back to the sctk's downstream. They are handled right in the event loop, 157 | /// unlike the ones coming from buffers on the `WindowHandle`'s. 158 | pub window_compositor_updates: HashMap, 159 | /// Layer Surface updates, which are coming from SCTK or the compositor, which require 160 | /// calling back to the sctk's downstream. They are handled right in the event loop, 161 | /// unlike the ones coming from buffers on the `WindowHandle`'s. 162 | pub layer_surface_compositor_updates: HashMap, 163 | 164 | /// A sink for window and device events that is being filled during dispatching 165 | /// event loop and forwarded downstream afterwards. 166 | pub(crate) sctk_events: Vec, 167 | /// Window updates comming from the user requests. Those are separatelly dispatched right after 168 | /// `MainEventsCleared`. 169 | pub window_user_requests: HashMap, 170 | /// Layer Surface updates comming from the user requests. Those are separatelly dispatched right after 171 | /// `MainEventsCleared`. 172 | pub layer_surface_user_requests: HashMap, 173 | /// Window updates comming from the user requests. Those are separatelly dispatched right after 174 | /// `MainEventsCleared`. 175 | pub popup_user_requests: HashMap, 176 | 177 | /// pending user events 178 | pub pending_user_events: Vec>, 179 | 180 | // handles 181 | pub(crate) queue_handle: QueueHandle, 182 | pub(crate) loop_handle: LoopHandle<'static, Self>, 183 | 184 | // sctk state objects 185 | pub(crate) registry_state: RegistryState, 186 | pub(crate) seat_state: SeatState, 187 | pub(crate) output_state: OutputState, 188 | pub(crate) compositor_state: CompositorState, 189 | pub(crate) shm_state: ShmState, 190 | pub(crate) xdg_shell_state: XdgShellState, 191 | pub(crate) xdg_window_state: XdgWindowState, 192 | pub(crate) layer_shell: Option, 193 | 194 | pub(crate) connection: Connection, 195 | } 196 | 197 | /// An error that occurred while running an application. 198 | #[derive(Debug, thiserror::Error)] 199 | pub enum PopupCreationError { 200 | /// Positioner creation failed 201 | #[error("Positioner creation failed")] 202 | PositionerCreationFailed(GlobalError), 203 | 204 | /// The specified parent is missing 205 | #[error("The specified parent is missing")] 206 | ParentMissing, 207 | 208 | /// Popup creation failed 209 | #[error("Popup creation failed")] 210 | PopupCreationFailed(GlobalError), 211 | } 212 | 213 | /// An error that occurred while running an application. 214 | #[derive(Debug, thiserror::Error)] 215 | pub enum LayerSurfaceCreationError { 216 | /// Layer shell is not supported by the compositor 217 | #[error("Layer shell is not supported by the compositor")] 218 | LayerShellNotSupported, 219 | 220 | /// WlSurface creation failed 221 | #[error("WlSurface creation failed")] 222 | WlSurfaceCreationFailed(GlobalError), 223 | 224 | /// LayerSurface creation failed 225 | #[error("Layer Surface creation failed")] 226 | LayerSurfaceCreationFailed(GlobalError), 227 | } 228 | 229 | impl SctkState 230 | where 231 | T: 'static + Debug, 232 | { 233 | pub fn get_popup( 234 | &mut self, 235 | settings: SctkPopupSettings, 236 | ) -> Result<(window::Id, WlSurface, WlSurface, WlSurface), PopupCreationError> { 237 | let positioner = XdgPositioner::new(&self.xdg_shell_state) 238 | .map_err(|e| PopupCreationError::PositionerCreationFailed(e))?; 239 | positioner.set_anchor(settings.positioner.anchor); 240 | positioner.set_anchor_rect( 241 | settings.positioner.anchor_rect.x, 242 | settings.positioner.anchor_rect.y, 243 | settings.positioner.anchor_rect.width, 244 | settings.positioner.anchor_rect.height, 245 | ); 246 | positioner.set_constraint_adjustment(settings.positioner.constraint_adjustment); 247 | positioner.set_gravity(settings.positioner.gravity); 248 | positioner.set_offset(settings.positioner.offset.0, settings.positioner.offset.1); 249 | if settings.positioner.reactive { 250 | positioner.set_reactive(); 251 | } 252 | positioner.set_size( 253 | settings.positioner.size.0 as i32, 254 | settings.positioner.size.1 as i32, 255 | ); 256 | 257 | if let Some(parent) = self.layer_surfaces.iter().find(|l| l.id == settings.parent) { 258 | let wl_surface = self.compositor_state.create_surface(&self.queue_handle); 259 | let popup = Popup::from_surface( 260 | None, 261 | &positioner, 262 | &self.queue_handle, 263 | wl_surface.clone(), 264 | &self.xdg_shell_state, 265 | ) 266 | .map_err(|e| PopupCreationError::PopupCreationFailed(e))?; 267 | 268 | parent.surface.get_popup(popup.xdg_popup()); 269 | wl_surface.commit(); 270 | self.popups.push(SctkPopup { 271 | id: settings.id, 272 | popup: popup.clone(), 273 | parent: SctkSurface::LayerSurface(parent.surface.wl_surface().clone()), 274 | toplevel: parent.surface.wl_surface().clone(), 275 | requested_size: settings.positioner.size, 276 | last_configure: None, 277 | pending_requests: Default::default(), 278 | }); 279 | Ok(( 280 | settings.id, 281 | parent.surface.wl_surface().clone(), 282 | parent.surface.wl_surface().clone(), 283 | popup.wl_surface().clone(), 284 | )) 285 | } else if let Some(parent) = self.windows.iter().find(|w| w.id == settings.parent) { 286 | let popup = Popup::new( 287 | parent.window.xdg_surface(), 288 | &positioner, 289 | &self.queue_handle, 290 | &self.compositor_state, 291 | &self.xdg_shell_state, 292 | ) 293 | .map_err(|e| PopupCreationError::PopupCreationFailed(e))?; 294 | self.popups.push(SctkPopup { 295 | id: settings.id, 296 | popup: popup.clone(), 297 | parent: SctkSurface::Window(parent.window.wl_surface().clone()), 298 | toplevel: parent.window.wl_surface().clone(), 299 | requested_size: settings.positioner.size, 300 | last_configure: None, 301 | pending_requests: Default::default(), 302 | }); 303 | Ok(( 304 | settings.id, 305 | parent.window.wl_surface().clone(), 306 | parent.window.wl_surface().clone(), 307 | popup.wl_surface().clone(), 308 | )) 309 | } else if let Some(i) = self.popups.iter().position(|p| p.id == settings.parent) { 310 | let (popup, parent, toplevel) = { 311 | let parent = &self.popups[i]; 312 | ( 313 | Popup::new( 314 | parent.popup.xdg_surface(), 315 | &positioner, 316 | &self.queue_handle, 317 | &self.compositor_state, 318 | &self.xdg_shell_state, 319 | ) 320 | .map_err(|e| PopupCreationError::PopupCreationFailed(e))?, 321 | parent.popup.wl_surface().clone(), 322 | parent.toplevel.clone(), 323 | ) 324 | }; 325 | self.popups.push(SctkPopup { 326 | id: settings.id, 327 | popup: popup.clone(), 328 | parent: SctkSurface::Popup(parent.clone()), 329 | toplevel: toplevel.clone(), 330 | requested_size: settings.positioner.size, 331 | last_configure: None, 332 | pending_requests: Default::default(), 333 | }); 334 | Ok(( 335 | settings.id, 336 | parent, 337 | toplevel, 338 | popup.wl_surface().clone(), 339 | )) 340 | } else { 341 | Err(PopupCreationError::ParentMissing) 342 | } 343 | } 344 | 345 | pub fn get_window(&mut self, settings: SctkWindowSettings) -> (window::Id, WlSurface) { 346 | let SctkWindowSettings { 347 | iced_settings: 348 | window::Settings { 349 | size, 350 | min_size, 351 | max_size, 352 | decorations, 353 | transparent, 354 | icon, 355 | .. 356 | }, 357 | window_id, 358 | app_id, 359 | title, 360 | parent, 361 | } = settings; 362 | // TODO Ashley: set window as opaque if transparency is false 363 | // TODO Ashley: set icon 364 | // TODO Ashley: save settings for window 365 | // TODO Ashley: decorations 366 | let wl_surface = self.compositor_state.create_surface(&self.queue_handle); 367 | let mut builder = if let Some(app_id) = app_id { 368 | Window::builder().app_id(app_id) 369 | } else { 370 | Window::builder() 371 | }; 372 | builder = if let Some(min_size) = min_size { 373 | builder.min_size(min_size) 374 | } else { 375 | builder 376 | }; 377 | builder = if let Some(max_size) = max_size { 378 | builder.max_size(max_size) 379 | } else { 380 | builder 381 | }; 382 | builder = if let Some(title) = title { 383 | builder.title(title) 384 | } else { 385 | builder 386 | }; 387 | 388 | // builder = if let Some(parent) = parent.and_then(|p| self.windows.iter().find(|w| w.window.wl_surface().id() == p)) { 389 | // builder.parent(&parent.window) 390 | // } else { 391 | // builder 392 | // }; 393 | let window = builder 394 | .decorations(if decorations { 395 | WindowDecorations::RequestServer 396 | } else { 397 | WindowDecorations::RequestClient 398 | }) 399 | .map( 400 | &self.queue_handle, 401 | &self.xdg_shell_state, 402 | &mut self.xdg_window_state, 403 | wl_surface.clone(), 404 | ) 405 | .expect("failed to create window"); 406 | 407 | window 408 | .xdg_surface() 409 | .set_window_geometry(0, 0, size.0 as i32, size.1 as i32); 410 | window.wl_surface().commit(); 411 | self.windows.push(SctkWindow { 412 | id: window_id, 413 | window, 414 | requested_size: Some(size), 415 | current_size: Some((1, 1)), 416 | last_configure: None, 417 | pending_requests: Vec::new(), 418 | }); 419 | (window_id, wl_surface) 420 | } 421 | 422 | pub fn get_layer_surface( 423 | &mut self, 424 | SctkLayerSurfaceSettings { 425 | id, 426 | layer, 427 | keyboard_interactivity, 428 | anchor, 429 | output, 430 | namespace, 431 | margin, 432 | size, 433 | exclusive_zone, 434 | }: SctkLayerSurfaceSettings, 435 | ) -> Result<(iced_native::window::Id, WlSurface), LayerSurfaceCreationError> { 436 | let layer_shell = self 437 | .layer_shell 438 | .as_ref() 439 | .ok_or(LayerSurfaceCreationError::LayerShellNotSupported)?; 440 | let wl_surface = self.compositor_state.create_surface(&self.queue_handle); 441 | 442 | let layer_surface = LayerSurface::builder() 443 | .anchor(anchor) 444 | .keyboard_interactivity(keyboard_interactivity) 445 | .margin(margin.top, margin.right, margin.bottom, margin.left) 446 | .size((size.0.unwrap_or_default(), size.1.unwrap_or_default())) 447 | .namespace(namespace) 448 | .exclusive_zone(exclusive_zone) 449 | .map(&self.queue_handle, layer_shell, wl_surface.clone(), layer) 450 | .map_err(|g_err| LayerSurfaceCreationError::LayerSurfaceCreationFailed(g_err))?; 451 | self.layer_surfaces.push(SctkLayerSurface { 452 | id, 453 | surface: layer_surface, 454 | requested_size: (None, None), 455 | current_size: None, 456 | layer, 457 | // builder needs to be refactored such that these fields are accessible 458 | anchor, 459 | keyboard_interactivity, 460 | margin, 461 | exclusive_zone, 462 | last_configure: None, 463 | pending_requests: Vec::new(), 464 | }); 465 | Ok((id, wl_surface)) 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /src/handlers/compositor.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0-only 2 | use sctk::{ 3 | compositor::CompositorHandler, 4 | delegate_compositor, 5 | reexports::client::{protocol::wl_surface, Connection, Proxy, QueueHandle}, 6 | }; 7 | use std::fmt::Debug; 8 | 9 | use crate::{event_loop::state::SctkState, sctk_event::SctkEvent}; 10 | 11 | impl CompositorHandler for SctkState { 12 | fn scale_factor_changed( 13 | &mut self, 14 | _conn: &Connection, 15 | _qh: &QueueHandle, 16 | surface: &wl_surface::WlSurface, 17 | new_factor: i32, 18 | ) { 19 | if let Some(w) = self 20 | .windows 21 | .iter() 22 | .find(|w| w.window.wl_surface().id() == surface.id()) 23 | { 24 | if let Some(e) = self.window_compositor_updates.get_mut(&surface.id()) { 25 | e.scale_factor = Some(new_factor) 26 | } 27 | } 28 | if let Some(w) = self 29 | .layer_surfaces 30 | .iter() 31 | .find(|w| w.surface.wl_surface().id() == surface.id()) 32 | { 33 | if let Some(e) = self.layer_surface_compositor_updates.get_mut(&surface.id()) { 34 | e.scale_factor = Some(new_factor) 35 | } 36 | } 37 | if let Some(w) = self 38 | .popups 39 | .iter() 40 | .find(|w| w.popup.wl_surface().id() == surface.id()) 41 | { 42 | if let Some(e) = self.popup_compositor_updates.get_mut(&surface.id()) { 43 | e.scale_factor = Some(new_factor) 44 | } 45 | } 46 | } 47 | 48 | fn frame( 49 | &mut self, 50 | _conn: &Connection, 51 | _qh: &QueueHandle, 52 | surface: &wl_surface::WlSurface, 53 | _time: u32, 54 | ) { 55 | self.sctk_events.push(SctkEvent::Draw(surface.clone())); 56 | } 57 | } 58 | 59 | delegate_compositor!(@ SctkState); 60 | -------------------------------------------------------------------------------- /src/handlers/data_device/data_device.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop-os/iced-sctk/6132680fc4233daccd900e0e035e392d3900e71d/src/handlers/data_device/data_device.rs -------------------------------------------------------------------------------- /src/handlers/data_device/data_offer.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop-os/iced-sctk/6132680fc4233daccd900e0e035e392d3900e71d/src/handlers/data_device/data_offer.rs -------------------------------------------------------------------------------- /src/handlers/data_device/data_source.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop-os/iced-sctk/6132680fc4233daccd900e0e035e392d3900e71d/src/handlers/data_device/data_source.rs -------------------------------------------------------------------------------- /src/handlers/data_device/mod.rs: -------------------------------------------------------------------------------- 1 | // TODO after merge 2 | -------------------------------------------------------------------------------- /src/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | // handlers 2 | pub mod compositor; 3 | pub mod data_device; 4 | pub mod output; 5 | pub mod seat; 6 | pub mod shell; 7 | 8 | use sctk::{ 9 | delegate_registry, delegate_shm, 10 | output::OutputState, 11 | registry::{ProvidesRegistryState, RegistryState}, 12 | registry_handlers, 13 | seat::SeatState, 14 | shm::{ShmHandler, ShmState}, 15 | }; 16 | use std::fmt::Debug; 17 | 18 | use crate::event_loop::state::SctkState; 19 | 20 | impl ShmHandler for SctkState { 21 | fn shm_state(&mut self) -> &mut ShmState { 22 | &mut self.shm_state 23 | } 24 | } 25 | 26 | impl ProvidesRegistryState for SctkState 27 | where 28 | T: 'static, 29 | { 30 | fn registry(&mut self) -> &mut RegistryState { 31 | &mut self.registry_state 32 | } 33 | registry_handlers![OutputState, SeatState,]; 34 | } 35 | 36 | delegate_shm!(@ SctkState); 37 | delegate_registry!(@ SctkState); 38 | -------------------------------------------------------------------------------- /src/handlers/output.rs: -------------------------------------------------------------------------------- 1 | use crate::{event_loop::state::SctkState, sctk_event::SctkEvent}; 2 | use sctk::{delegate_output, output::OutputHandler, reexports::client::Proxy}; 3 | use std::fmt::Debug; 4 | 5 | impl OutputHandler for SctkState { 6 | fn output_state(&mut self) -> &mut sctk::output::OutputState { 7 | &mut self.output_state 8 | } 9 | 10 | fn new_output( 11 | &mut self, 12 | _conn: &sctk::reexports::client::Connection, 13 | _qh: &sctk::reexports::client::QueueHandle, 14 | output: sctk::reexports::client::protocol::wl_output::WlOutput, 15 | ) { 16 | self.sctk_events.push(SctkEvent::NewOutput { 17 | id: output.clone(), 18 | info: self.output_state.info(&output), 19 | }); 20 | self.outputs.push(output); 21 | } 22 | 23 | fn update_output( 24 | &mut self, 25 | _conn: &sctk::reexports::client::Connection, 26 | _qh: &sctk::reexports::client::QueueHandle, 27 | output: sctk::reexports::client::protocol::wl_output::WlOutput, 28 | ) { 29 | if let Some(info) = self.output_state.info(&output) { 30 | self.sctk_events.push(SctkEvent::UpdateOutput { 31 | id: output.clone(), 32 | info, 33 | }); 34 | } 35 | } 36 | 37 | fn output_destroyed( 38 | &mut self, 39 | _conn: &sctk::reexports::client::Connection, 40 | _qh: &sctk::reexports::client::QueueHandle, 41 | output: sctk::reexports::client::protocol::wl_output::WlOutput, 42 | ) { 43 | self.sctk_events.push(SctkEvent::RemovedOutput(output.id())); 44 | // TODO clean up any layer surfaces on this output? 45 | } 46 | } 47 | 48 | delegate_output!(@ SctkState); 49 | -------------------------------------------------------------------------------- /src/handlers/seat/keyboard.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | event_loop::state::SctkState, 3 | sctk_event::{KeyboardEventVariant, SctkEvent}, 4 | }; 5 | 6 | use sctk::{delegate_keyboard, reexports::client::Proxy, seat::keyboard::KeyboardHandler}; 7 | use std::fmt::Debug; 8 | 9 | impl KeyboardHandler for SctkState { 10 | fn enter( 11 | &mut self, 12 | _conn: &sctk::reexports::client::Connection, 13 | _qh: &sctk::reexports::client::QueueHandle, 14 | keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, 15 | surface: &sctk::reexports::client::protocol::wl_surface::WlSurface, 16 | _serial: u32, 17 | _raw: &[u32], 18 | _keysyms: &[u32], 19 | ) { 20 | let (i, mut is_active, seat) = { 21 | let (i, is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { 22 | if s.kbd.as_ref() == Some(keyboard) { 23 | Some((i, s)) 24 | } else { 25 | None 26 | } 27 | }) { 28 | Some((i, s)) => (i, i == 0, s), 29 | None => return, 30 | }; 31 | my_seat.kbd_focus.replace(surface.clone()); 32 | 33 | let seat = my_seat.seat.clone(); 34 | (i, is_active, seat) 35 | }; 36 | 37 | // TODO Ashley: thoroughly test this 38 | // swap the active seat to be the current seat if the current "active" seat is not focused on the application anyway 39 | if !is_active && self.seats[0].kbd_focus.is_none() { 40 | is_active = true; 41 | self.seats.swap(0, i); 42 | } 43 | 44 | if is_active { 45 | self.sctk_events.push(SctkEvent::KeyboardEvent { 46 | variant: KeyboardEventVariant::Enter(surface.clone()), 47 | kbd_id: keyboard.clone(), 48 | seat_id: seat.clone(), 49 | }) 50 | } 51 | } 52 | 53 | fn leave( 54 | &mut self, 55 | _conn: &sctk::reexports::client::Connection, 56 | _qh: &sctk::reexports::client::QueueHandle, 57 | keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, 58 | surface: &sctk::reexports::client::protocol::wl_surface::WlSurface, 59 | _serial: u32, 60 | ) { 61 | let (is_active, seat, kbd) = { 62 | let (is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { 63 | if s.kbd.as_ref() == Some(keyboard) { 64 | Some((i, s)) 65 | } else { 66 | None 67 | } 68 | }) { 69 | Some((i, s)) => (i == 0, s), 70 | None => return, 71 | }; 72 | let seat = my_seat.seat.clone(); 73 | let kbd = keyboard.clone(); 74 | my_seat.kbd_focus.take(); 75 | (is_active, seat, kbd) 76 | }; 77 | 78 | if is_active { 79 | self.sctk_events.push(SctkEvent::KeyboardEvent { 80 | variant: KeyboardEventVariant::Leave(surface.clone()), 81 | kbd_id: kbd, 82 | seat_id: seat, 83 | }); 84 | // if there is another seat with a keyboard focused on a surface make that the new active seat 85 | if let Some(i) = self.seats.iter().position(|s| s.kbd_focus.is_some()) { 86 | self.seats.swap(0, i); 87 | let s = &self.seats[0]; 88 | self.sctk_events.push(SctkEvent::KeyboardEvent { 89 | variant: KeyboardEventVariant::Enter(s.kbd_focus.clone().unwrap()), 90 | kbd_id: s.kbd.clone().unwrap(), 91 | seat_id: s.seat.clone(), 92 | }) 93 | } 94 | } 95 | } 96 | 97 | fn press_key( 98 | &mut self, 99 | _conn: &sctk::reexports::client::Connection, 100 | _qh: &sctk::reexports::client::QueueHandle, 101 | keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, 102 | _serial: u32, 103 | event: sctk::seat::keyboard::KeyEvent, 104 | ) { 105 | let (is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { 106 | if s.kbd.as_ref() == Some(keyboard) { 107 | Some((i, s)) 108 | } else { 109 | None 110 | } 111 | }) { 112 | Some((i, s)) => (i == 0, s), 113 | None => return, 114 | }; 115 | let seat_id = my_seat.seat.clone(); 116 | let kbd_id = keyboard.clone(); 117 | my_seat.last_kbd_press.replace(event.clone()); 118 | if is_active { 119 | self.sctk_events.push(SctkEvent::KeyboardEvent { 120 | variant: KeyboardEventVariant::Press(event), 121 | kbd_id, 122 | seat_id, 123 | }); 124 | } 125 | } 126 | 127 | fn release_key( 128 | &mut self, 129 | _conn: &sctk::reexports::client::Connection, 130 | _qh: &sctk::reexports::client::QueueHandle, 131 | keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, 132 | _serial: u32, 133 | event: sctk::seat::keyboard::KeyEvent, 134 | ) { 135 | let (is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { 136 | if s.kbd.as_ref() == Some(keyboard) { 137 | Some((i, s)) 138 | } else { 139 | None 140 | } 141 | }) { 142 | Some((i, s)) => (i == 0, s), 143 | None => return, 144 | }; 145 | let seat_id = my_seat.seat.clone(); 146 | let kbd_id = keyboard.clone(); 147 | 148 | if is_active { 149 | self.sctk_events.push(SctkEvent::KeyboardEvent { 150 | variant: KeyboardEventVariant::Release(event), 151 | kbd_id, 152 | seat_id, 153 | }); 154 | } 155 | } 156 | 157 | fn update_modifiers( 158 | &mut self, 159 | _conn: &sctk::reexports::client::Connection, 160 | _qh: &sctk::reexports::client::QueueHandle, 161 | keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, 162 | _serial: u32, 163 | modifiers: sctk::seat::keyboard::Modifiers, 164 | ) { 165 | let (is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { 166 | if s.kbd.as_ref() == Some(keyboard) { 167 | Some((i, s)) 168 | } else { 169 | None 170 | } 171 | }) { 172 | Some((i, s)) => (i == 0, s), 173 | None => return, 174 | }; 175 | let seat_id = my_seat.seat.clone(); 176 | let kbd_id = keyboard.clone(); 177 | 178 | if is_active { 179 | self.sctk_events.push(SctkEvent::KeyboardEvent { 180 | variant: KeyboardEventVariant::Modifiers(modifiers), 181 | kbd_id, 182 | seat_id, 183 | }) 184 | } 185 | } 186 | } 187 | 188 | delegate_keyboard!(@ SctkState); 189 | -------------------------------------------------------------------------------- /src/handlers/seat/mod.rs: -------------------------------------------------------------------------------- 1 | // TODO support multi-seat handling 2 | pub mod keyboard; 3 | pub mod pointer; 4 | pub mod seat; 5 | pub mod touch; 6 | -------------------------------------------------------------------------------- /src/handlers/seat/pointer.rs: -------------------------------------------------------------------------------- 1 | use crate::{event_loop::state::SctkState, sctk_event::SctkEvent}; 2 | use sctk::{ 3 | delegate_pointer, 4 | reexports::client::Proxy, 5 | seat::pointer::{PointerEventKind, PointerHandler}, 6 | }; 7 | use std::fmt::Debug; 8 | 9 | impl PointerHandler for SctkState { 10 | fn pointer_frame( 11 | &mut self, 12 | _conn: &sctk::reexports::client::Connection, 13 | _qh: &sctk::reexports::client::QueueHandle, 14 | pointer: &sctk::reexports::client::protocol::wl_pointer::WlPointer, 15 | events: &[sctk::seat::pointer::PointerEvent], 16 | ) { 17 | let (is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { 18 | if s.ptr.as_ref() == Some(pointer) { 19 | Some((i, s)) 20 | } else { 21 | None 22 | } 23 | }) { 24 | Some((i, s)) => (i == 0, s), 25 | None => return, 26 | }; 27 | 28 | // track events, but only forward for the active seat 29 | for e in events { 30 | if is_active { 31 | self.sctk_events.push(SctkEvent::PointerEvent { 32 | variant: e.clone(), 33 | ptr_id: pointer.clone(), 34 | seat_id: my_seat.seat.clone(), 35 | }); 36 | } 37 | match e.kind { 38 | PointerEventKind::Enter { .. } => { 39 | my_seat.ptr_focus.replace(e.surface.clone()); 40 | } 41 | PointerEventKind::Leave { .. } => { 42 | my_seat.ptr_focus.take(); 43 | } 44 | PointerEventKind::Press { 45 | time, 46 | button, 47 | serial, 48 | } => { 49 | my_seat.last_ptr_press.replace((time, button, serial)); 50 | } 51 | // TODO revisit events that ought to be handled and change internal state 52 | _ => {} 53 | } 54 | } 55 | } 56 | } 57 | 58 | delegate_pointer!(@ SctkState); 59 | -------------------------------------------------------------------------------- /src/handlers/seat/seat.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | event_loop::{state::SctkSeat, state::SctkState}, 3 | sctk_event::{KeyboardEventVariant, SctkEvent, SeatEventVariant}, 4 | }; 5 | use iced_native::keyboard::Modifiers; 6 | use sctk::{delegate_seat, reexports::client::Proxy, seat::SeatHandler}; 7 | use std::fmt::Debug; 8 | 9 | impl SeatHandler for SctkState 10 | where 11 | T: 'static, 12 | { 13 | fn seat_state(&mut self) -> &mut sctk::seat::SeatState { 14 | &mut self.seat_state 15 | } 16 | 17 | fn new_seat( 18 | &mut self, 19 | _conn: &sctk::reexports::client::Connection, 20 | _qh: &sctk::reexports::client::QueueHandle, 21 | seat: sctk::reexports::client::protocol::wl_seat::WlSeat, 22 | ) { 23 | self.sctk_events.push(SctkEvent::SeatEvent { 24 | variant: SeatEventVariant::New, 25 | id: seat.clone(), 26 | }); 27 | self.seats.push(SctkSeat { 28 | seat, 29 | kbd: None, 30 | ptr: None, 31 | touch: None, 32 | data_device: None, 33 | modifiers: Modifiers::default(), 34 | kbd_focus: None, 35 | ptr_focus: None, 36 | last_ptr_press: None, 37 | last_kbd_press: None, 38 | }); 39 | } 40 | 41 | fn new_capability( 42 | &mut self, 43 | _conn: &sctk::reexports::client::Connection, 44 | qh: &sctk::reexports::client::QueueHandle, 45 | seat: sctk::reexports::client::protocol::wl_seat::WlSeat, 46 | capability: sctk::seat::Capability, 47 | ) { 48 | let my_seat = match self.seats.iter_mut().find(|s| s.seat == seat) { 49 | Some(s) => s, 50 | None => { 51 | self.seats.push(SctkSeat { 52 | seat: seat.clone(), 53 | kbd: None, 54 | ptr: None, 55 | touch: None, 56 | data_device: None, 57 | modifiers: Modifiers::default(), 58 | kbd_focus: None, 59 | ptr_focus: None, 60 | last_ptr_press: None, 61 | last_kbd_press: None, 62 | }); 63 | self.seats.last_mut().unwrap() 64 | } 65 | }; 66 | // TODO data device 67 | match capability { 68 | sctk::seat::Capability::Keyboard => { 69 | if let Ok((kbd, source)) = self.seat_state.get_keyboard_with_repeat(qh, &seat, None) 70 | { 71 | self.sctk_events.push(SctkEvent::SeatEvent { 72 | variant: SeatEventVariant::NewCapability(capability, kbd.id()), 73 | id: seat.clone(), 74 | }); 75 | let kbd_clone = kbd.clone(); 76 | self.loop_handle 77 | .insert_source(source, move |e, _, state| { 78 | state.sctk_events.push(SctkEvent::KeyboardEvent { 79 | variant: KeyboardEventVariant::Repeat(e), 80 | kbd_id: kbd_clone.clone(), 81 | seat_id: seat.clone(), 82 | }); 83 | }) 84 | .expect("Failed to insert the repeating keyboard into the event loop"); 85 | my_seat.kbd.replace(kbd); 86 | } 87 | } 88 | sctk::seat::Capability::Pointer => { 89 | if let Ok(ptr) = self.seat_state.get_pointer(qh, &seat) { 90 | self.sctk_events.push(SctkEvent::SeatEvent { 91 | variant: SeatEventVariant::NewCapability(capability, ptr.id()), 92 | id: seat.clone(), 93 | }); 94 | my_seat.ptr.replace(ptr); 95 | } 96 | } 97 | sctk::seat::Capability::Touch => { 98 | // TODO touch 99 | } 100 | _ => unimplemented!(), 101 | } 102 | } 103 | 104 | fn remove_capability( 105 | &mut self, 106 | _conn: &sctk::reexports::client::Connection, 107 | _qh: &sctk::reexports::client::QueueHandle, 108 | seat: sctk::reexports::client::protocol::wl_seat::WlSeat, 109 | capability: sctk::seat::Capability, 110 | ) { 111 | let my_seat = match self.seats.iter_mut().find(|s| s.seat == seat) { 112 | Some(s) => s, 113 | None => return, 114 | }; 115 | 116 | // TODO data device 117 | match capability { 118 | // TODO use repeating kbd? 119 | sctk::seat::Capability::Keyboard => { 120 | if let Some(kbd) = my_seat.kbd.take() { 121 | self.sctk_events.push(SctkEvent::SeatEvent { 122 | variant: SeatEventVariant::RemoveCapability(capability, kbd.id()), 123 | id: seat.clone(), 124 | }); 125 | } 126 | } 127 | sctk::seat::Capability::Pointer => { 128 | if let Some(ptr) = my_seat.ptr.take() { 129 | self.sctk_events.push(SctkEvent::SeatEvent { 130 | variant: SeatEventVariant::RemoveCapability(capability, ptr.id()), 131 | id: seat.clone(), 132 | }); 133 | } 134 | } 135 | sctk::seat::Capability::Touch => { 136 | // TODO touch 137 | // my_seat.touch = self.seat_state.get_touch(qh, &seat).ok(); 138 | } 139 | _ => unimplemented!(), 140 | } 141 | } 142 | 143 | fn remove_seat( 144 | &mut self, 145 | _conn: &sctk::reexports::client::Connection, 146 | _qh: &sctk::reexports::client::QueueHandle, 147 | seat: sctk::reexports::client::protocol::wl_seat::WlSeat, 148 | ) { 149 | self.sctk_events.push(SctkEvent::SeatEvent { 150 | variant: SeatEventVariant::Remove, 151 | id: seat.clone(), 152 | }); 153 | if let Some(i) = self.seats.iter().position(|s| s.seat == seat) { 154 | self.seats.remove(i); 155 | } 156 | } 157 | } 158 | 159 | delegate_seat!(@ SctkState); 160 | -------------------------------------------------------------------------------- /src/handlers/seat/touch.rs: -------------------------------------------------------------------------------- 1 | // TODO 2 | -------------------------------------------------------------------------------- /src/handlers/shell/layer.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | dpi::LogicalSize, 3 | event_loop::state::SctkState, 4 | sctk_event::{LayerSurfaceEventVariant, SctkEvent}, 5 | }; 6 | use sctk::{ 7 | delegate_layer, 8 | reexports::client::Proxy, 9 | shell::layer::{Anchor, KeyboardInteractivity, LayerShellHandler}, 10 | }; 11 | use std::fmt::Debug; 12 | 13 | impl LayerShellHandler for SctkState { 14 | fn closed( 15 | &mut self, 16 | _conn: &sctk::reexports::client::Connection, 17 | _qh: &sctk::reexports::client::QueueHandle, 18 | layer: &sctk::shell::layer::LayerSurface, 19 | ) { 20 | let layer = match self 21 | .layer_surfaces 22 | .iter() 23 | .position(|s| s.surface.wl_surface().id() == layer.wl_surface().id()) 24 | { 25 | Some(w) => self.layer_surfaces.remove(w), 26 | None => return, 27 | }; 28 | 29 | self.sctk_events.push(SctkEvent::LayerSurfaceEvent { 30 | variant: LayerSurfaceEventVariant::Done, 31 | id: layer.surface.wl_surface().clone(), 32 | }) 33 | // TODO popup cleanup 34 | } 35 | 36 | fn configure( 37 | &mut self, 38 | _conn: &sctk::reexports::client::Connection, 39 | _qh: &sctk::reexports::client::QueueHandle, 40 | layer: &sctk::shell::layer::LayerSurface, 41 | mut configure: sctk::shell::layer::LayerSurfaceConfigure, 42 | _serial: u32, 43 | ) { 44 | let layer = match self 45 | .layer_surfaces 46 | .iter_mut() 47 | .find(|s| s.surface.wl_surface().id() == layer.wl_surface().id()) 48 | { 49 | Some(l) => l, 50 | None => return, 51 | }; 52 | let id = layer.surface.wl_surface().id(); 53 | configure.new_size.0 = if configure.new_size.0 > 0 { 54 | configure.new_size.0 55 | } else { 56 | layer.requested_size.0.unwrap_or(1) 57 | }; 58 | configure.new_size.1 = if configure.new_size.1 > 0 { 59 | configure.new_size.1 60 | } else { 61 | layer.requested_size.1.unwrap_or(1) 62 | }; 63 | layer 64 | .current_size 65 | .replace(LogicalSize::new(configure.new_size.0, configure.new_size.1)); 66 | let first = layer.last_configure.is_none(); 67 | layer.last_configure.replace(configure.clone()); 68 | 69 | self.sctk_events.push(SctkEvent::LayerSurfaceEvent { 70 | variant: LayerSurfaceEventVariant::Configure( 71 | configure, 72 | layer.surface.wl_surface().clone(), 73 | first, 74 | ), 75 | id: layer.surface.wl_surface().clone(), 76 | }); 77 | self.sctk_events.push(SctkEvent::Draw(layer.surface.wl_surface().clone())); 78 | } 79 | } 80 | 81 | delegate_layer!(@ SctkState); 82 | 83 | /// A request to SCTK window from Winit window. 84 | #[derive(Debug, Clone)] 85 | pub enum LayerSurfaceRequest { 86 | /// Set fullscreen. 87 | /// 88 | /// Passing `None` will set it on the current monitor. 89 | Size(LogicalSize), 90 | 91 | /// Unset fullscreen. 92 | UnsetFullscreen, 93 | 94 | /// Show cursor for the certain window or not. 95 | ShowCursor(bool), 96 | 97 | /// Set anchor 98 | Anchor(Anchor), 99 | 100 | /// Set margin 101 | ExclusiveZone(i32), 102 | 103 | /// Set margin 104 | Margin(u32), 105 | 106 | /// Passthrough mouse input to underlying windows. 107 | KeyboardInteractivity(KeyboardInteractivity), 108 | 109 | /// Redraw was requested. 110 | Redraw, 111 | 112 | /// Window should be closed. 113 | Close, 114 | } 115 | -------------------------------------------------------------------------------- /src/handlers/shell/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod layer; 2 | pub mod xdg_popup; 3 | pub mod xdg_window; 4 | -------------------------------------------------------------------------------- /src/handlers/shell/xdg_popup.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | commands::popup, 3 | event_loop::state::{self, SctkState, SctkSurface}, 4 | sctk_event::{PopupEventVariant, SctkEvent}, 5 | }; 6 | use sctk::{delegate_xdg_popup, reexports::client::Proxy, shell::xdg::popup::PopupHandler}; 7 | use std::fmt::Debug; 8 | 9 | impl PopupHandler for SctkState { 10 | fn configure( 11 | &mut self, 12 | _conn: &sctk::reexports::client::Connection, 13 | _qh: &sctk::reexports::client::QueueHandle, 14 | popup: &sctk::shell::xdg::popup::Popup, 15 | configure: sctk::shell::xdg::popup::PopupConfigure, 16 | ) { 17 | let sctk_popup = match self 18 | .popups 19 | .iter_mut() 20 | .find(|s| s.popup.wl_surface().clone() == popup.wl_surface().clone()) 21 | { 22 | Some(p) => p, 23 | None => return, 24 | }; 25 | let first = sctk_popup.last_configure.is_none(); 26 | sctk_popup.last_configure.replace(configure.clone()); 27 | 28 | self.sctk_events.push(SctkEvent::PopupEvent { 29 | variant: PopupEventVariant::Configure(configure, popup.wl_surface().clone(), first), 30 | id: popup.wl_surface().clone(), 31 | toplevel_id: sctk_popup.toplevel.clone(), 32 | parent_id: match &sctk_popup.parent { 33 | SctkSurface::LayerSurface(s) => s.clone(), 34 | SctkSurface::Window(s) => s.clone(), 35 | SctkSurface::Popup(s) => s.clone(), 36 | }, 37 | }) 38 | } 39 | 40 | fn done( 41 | &mut self, 42 | _conn: &sctk::reexports::client::Connection, 43 | _qh: &sctk::reexports::client::QueueHandle, 44 | popup: &sctk::shell::xdg::popup::Popup, 45 | ) { 46 | let sctk_popup = match self 47 | .popups 48 | .iter() 49 | .position(|s| s.popup.wl_surface().clone() == popup.wl_surface().clone()) 50 | { 51 | Some(p) => self.popups.remove(p), 52 | None => return, 53 | }; 54 | let mut to_destroy = vec![sctk_popup]; 55 | while let Some(popup_to_destroy) = to_destroy.last() { 56 | match popup_to_destroy.parent.clone() { 57 | state::SctkSurface::LayerSurface(_) | state::SctkSurface::Window(_) => { 58 | break; 59 | } 60 | state::SctkSurface::Popup(popup_to_destroy_first) => { 61 | let popup_to_destroy_first = self 62 | .popups 63 | .iter() 64 | .position(|p| p.popup.wl_surface() == &popup_to_destroy_first) 65 | .unwrap(); 66 | let popup_to_destroy_first = self.popups.remove(popup_to_destroy_first); 67 | to_destroy.push(popup_to_destroy_first); 68 | } 69 | } 70 | } 71 | for popup in to_destroy.into_iter().rev() { 72 | self.sctk_events.push(SctkEvent::PopupEvent { 73 | variant: PopupEventVariant::Done, 74 | toplevel_id: popup.toplevel.clone(), 75 | parent_id: popup.parent.wl_surface().clone(), 76 | id: popup.popup.wl_surface().clone(), 77 | }); 78 | self.popups.push(popup); 79 | } 80 | } 81 | } 82 | delegate_xdg_popup!(@ SctkState); 83 | -------------------------------------------------------------------------------- /src/handlers/shell/xdg_window.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | event_loop::state::SctkState, 3 | sctk_event::{SctkEvent, WindowEventVariant}, 4 | }; 5 | use sctk::{ 6 | delegate_xdg_shell, delegate_xdg_window, reexports::client::Proxy, 7 | shell::xdg::window::WindowHandler, 8 | }; 9 | use std::fmt::Debug; 10 | 11 | impl WindowHandler for SctkState { 12 | fn request_close( 13 | &mut self, 14 | _conn: &sctk::reexports::client::Connection, 15 | _qh: &sctk::reexports::client::QueueHandle, 16 | window: &sctk::shell::xdg::window::Window, 17 | ) { 18 | let window = match self 19 | .windows 20 | .iter() 21 | .position(|s| s.window.wl_surface() == window.wl_surface()) 22 | { 23 | Some(w) => self.windows.remove(w), 24 | None => return, 25 | }; 26 | 27 | self.sctk_events.push(SctkEvent::WindowEvent { 28 | variant: WindowEventVariant::Close, 29 | id: window.window.wl_surface().clone(), 30 | }) 31 | // TODO popup cleanup 32 | } 33 | 34 | fn configure( 35 | &mut self, 36 | _conn: &sctk::reexports::client::Connection, 37 | _qh: &sctk::reexports::client::QueueHandle, 38 | window: &sctk::shell::xdg::window::Window, 39 | mut configure: sctk::shell::xdg::window::WindowConfigure, 40 | _serial: u32, 41 | ) { 42 | let window = match self 43 | .windows 44 | .iter_mut() 45 | .find(|w| w.window.wl_surface() == window.wl_surface()) 46 | { 47 | Some(w) => w, 48 | None => return, 49 | }; 50 | 51 | if configure.new_size.is_none() { 52 | configure.new_size = Some(window.requested_size.unwrap_or((300, 500))); 53 | }; 54 | 55 | let wl_surface = window.window.wl_surface(); 56 | let id = wl_surface.clone(); 57 | let first = window.last_configure.is_none(); 58 | window.last_configure.replace(configure.clone()); 59 | 60 | self.sctk_events.push(SctkEvent::WindowEvent { 61 | variant: WindowEventVariant::Configure(configure, wl_surface.clone(), first), 62 | id, 63 | }) 64 | } 65 | } 66 | 67 | delegate_xdg_window!(@ SctkState); 68 | delegate_xdg_shell!(@ SctkState); 69 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use iced_native::*; 2 | 3 | pub mod application; 4 | pub mod commands; 5 | pub mod conversion; 6 | pub mod dpi; 7 | pub mod egl; 8 | pub mod error; 9 | pub mod event_loop; 10 | mod handlers; 11 | pub mod result; 12 | pub mod sctk_event; 13 | pub mod settings; 14 | pub mod util; 15 | pub mod window; 16 | 17 | pub use application::{run, Application}; 18 | pub use clipboard::Clipboard; 19 | pub use error::Error; 20 | pub use event_loop::proxy::Proxy; 21 | pub use settings::Settings; 22 | 23 | pub use iced_graphics::Viewport; 24 | pub use iced_native::window::Position; 25 | -------------------------------------------------------------------------------- /src/result.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | 3 | /// The result of running an [`Application`]. 4 | /// 5 | /// [`Application`]: crate::Application 6 | pub type Result = std::result::Result<(), Error>; 7 | -------------------------------------------------------------------------------- /src/sctk_event.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, time::Instant}; 2 | 3 | use crate::{ 4 | application::SurfaceIdWrapper, 5 | conversion::{ 6 | keysym_to_vkey, modifiers_to_native, pointer_axis_to_native, pointer_button_to_native, 7 | }, 8 | dpi::{LogicalSize, PhysicalSize}, 9 | }; 10 | use iced_graphics::Point; 11 | use iced_native::{ 12 | event::{ 13 | wayland::{self, LayerEvent, PopupEvent}, 14 | PlatformSpecific, 15 | }, 16 | keyboard::{self, KeyCode}, 17 | mouse, 18 | window::{self, Id as SurfaceId}, 19 | }; 20 | use sctk::{ 21 | output::OutputInfo, 22 | reexports::client::{backend::ObjectId, protocol::{wl_surface::WlSurface, wl_seat::{self, WlSeat}, wl_pointer::WlPointer, wl_keyboard::WlKeyboard, wl_output::WlOutput}, Proxy}, 23 | seat::{ 24 | keyboard::{KeyEvent, Modifiers}, 25 | pointer::{PointerEvent, PointerEventKind}, 26 | Capability, 27 | }, 28 | shell::{ 29 | layer::LayerSurfaceConfigure, 30 | xdg::{popup::PopupConfigure, window::WindowConfigure}, 31 | }, 32 | }; 33 | 34 | #[derive(Debug, Clone)] 35 | pub enum IcedSctkEvent { 36 | /// Emitted when new events arrive from the OS to be processed. 37 | /// 38 | /// This event type is useful as a place to put code that should be done before you start 39 | /// processing events, such as updating frame timing information for benchmarking or checking 40 | /// the [`StartCause`][crate::event::StartCause] to see if a timer set by 41 | /// [`ControlFlow::WaitUntil`](crate::event_loop::ControlFlow::WaitUntil) has elapsed. 42 | NewEvents(StartCause), 43 | 44 | /// Any user event from iced 45 | UserEvent(T), 46 | /// An event produced by sctk 47 | SctkEvent(SctkEvent), 48 | 49 | /// Emitted when all of the event loop's input events have been processed and redraw processing 50 | /// is about to begin. 51 | /// 52 | /// This event is useful as a place to put your code that should be run after all 53 | /// state-changing events have been handled and you want to do stuff (updating state, performing 54 | /// calculations, etc) that happens as the "main body" of your event loop. If your program only draws 55 | /// graphics when something changes, it's usually better to do it in response to 56 | /// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted 57 | /// immediately after this event. Programs that draw graphics continuously, like most games, 58 | /// can render here unconditionally for simplicity. 59 | MainEventsCleared, 60 | 61 | /// Emitted after [`MainEventsCleared`] when a window should be redrawn. 62 | /// 63 | /// This gets triggered in two scenarios: 64 | /// - The OS has performed an operation that's invalidated the window's contents (such as 65 | /// resizing the window). 66 | /// - The application has explicitly requested a redraw via [`Window::request_redraw`]. 67 | /// 68 | /// During each iteration of the event loop, Winit will aggregate duplicate redraw requests 69 | /// into a single event, to help avoid duplicating rendering work. 70 | /// 71 | /// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless 72 | /// something changes, like most non-game GUIs. 73 | /// 74 | /// [`MainEventsCleared`]: Self::MainEventsCleared 75 | RedrawRequested(ObjectId), 76 | 77 | /// Emitted after all [`RedrawRequested`] events have been processed and control flow is about to 78 | /// be taken away from the program. If there are no `RedrawRequested` events, it is emitted 79 | /// immediately after `MainEventsCleared`. 80 | /// 81 | /// This event is useful for doing any cleanup or bookkeeping work after all the rendering 82 | /// tasks have been completed. 83 | /// 84 | /// [`RedrawRequested`]: Self::RedrawRequested 85 | RedrawEventsCleared, 86 | 87 | /// Emitted when the event loop is being shut down. 88 | /// 89 | /// This is irreversible - if this event is emitted, it is guaranteed to be the last event that 90 | /// gets emitted. You generally want to treat this as an "do on quit" event. 91 | LoopDestroyed, 92 | } 93 | 94 | #[derive(Debug, Clone)] 95 | pub enum SctkEvent { 96 | // 97 | // Input events 98 | // 99 | SeatEvent { 100 | variant: SeatEventVariant, 101 | id: WlSeat, 102 | }, 103 | PointerEvent { 104 | variant: PointerEvent, 105 | ptr_id: WlPointer, 106 | seat_id: WlSeat, 107 | }, 108 | KeyboardEvent { 109 | variant: KeyboardEventVariant, 110 | kbd_id: WlKeyboard, 111 | seat_id: WlSeat, 112 | }, 113 | // TODO data device & touch 114 | 115 | // 116 | // Surface Events 117 | // 118 | WindowEvent { 119 | variant: WindowEventVariant, 120 | id: WlSurface, 121 | }, 122 | LayerSurfaceEvent { 123 | variant: LayerSurfaceEventVariant, 124 | id: WlSurface, 125 | }, 126 | PopupEvent { 127 | variant: PopupEventVariant, 128 | /// this may be the Id of a window or layer surface 129 | toplevel_id: WlSurface, 130 | /// this may be any SurfaceId 131 | parent_id: WlSurface, 132 | /// the id of this popup 133 | id: WlSurface, 134 | }, 135 | 136 | // 137 | // output events 138 | // 139 | NewOutput { 140 | id: WlOutput, 141 | info: Option, 142 | }, 143 | UpdateOutput { 144 | id: WlOutput, 145 | info: OutputInfo, 146 | }, 147 | RemovedOutput(ObjectId), 148 | 149 | // 150 | // compositor events 151 | // 152 | Draw(WlSurface), 153 | ScaleFactorChanged { 154 | factor: f64, 155 | id: WlOutput, 156 | inner_size: PhysicalSize, 157 | }, 158 | } 159 | 160 | #[derive(Debug, Clone)] 161 | pub enum SeatEventVariant { 162 | New, 163 | Remove, 164 | NewCapability(Capability, ObjectId), 165 | RemoveCapability(Capability, ObjectId), 166 | } 167 | 168 | #[derive(Debug, Clone)] 169 | pub enum KeyboardEventVariant { 170 | Leave(WlSurface), 171 | Enter(WlSurface), 172 | Press(KeyEvent), 173 | Repeat(KeyEvent), 174 | Release(KeyEvent), 175 | Modifiers(Modifiers), 176 | } 177 | 178 | #[derive(Debug, Clone)] 179 | pub enum WindowEventVariant { 180 | Created(ObjectId, SurfaceId), 181 | /// 182 | Close, 183 | /// 184 | WmCapabilities(Vec), 185 | /// 186 | ConfigureBounds { 187 | width: u32, 188 | height: u32, 189 | }, 190 | /// 191 | Configure(WindowConfigure, WlSurface, bool), 192 | } 193 | 194 | #[derive(Debug, Clone)] 195 | pub enum PopupEventVariant { 196 | Created(ObjectId, SurfaceId), 197 | /// 198 | Done, 199 | /// 200 | WmCapabilities(Vec), 201 | /// 202 | Configure(PopupConfigure, WlSurface, bool), 203 | /// 204 | RepositionionedPopup { 205 | token: u32, 206 | }, 207 | } 208 | 209 | #[derive(Debug, Clone)] 210 | pub enum LayerSurfaceEventVariant { 211 | /// sent after creation of the layer surface 212 | Created(ObjectId, SurfaceId), 213 | /// 214 | Done, 215 | /// 216 | Configure(LayerSurfaceConfigure, WlSurface, bool), 217 | } 218 | 219 | /// Describes the reason the event loop is resuming. 220 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 221 | pub enum StartCause { 222 | /// Sent if the time specified by [`ControlFlow::WaitUntil`] has been reached. Contains the 223 | /// moment the timeout was requested and the requested resume time. The actual resume time is 224 | /// guaranteed to be equal to or after the requested resume time. 225 | /// 226 | /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil 227 | ResumeTimeReached { 228 | start: Instant, 229 | requested_resume: Instant, 230 | }, 231 | 232 | /// Sent if the OS has new events to send to the window, after a wait was requested. Contains 233 | /// the moment the wait was requested and the resume time, if requested. 234 | WaitCancelled { 235 | start: Instant, 236 | requested_resume: Option, 237 | }, 238 | 239 | /// Sent if the event loop is being resumed after the loop's control flow was set to 240 | /// [`ControlFlow::Poll`]. 241 | /// 242 | /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll 243 | Poll, 244 | 245 | /// Sent once, immediately after `run` is called. Indicates that the loop was just initialized. 246 | Init, 247 | } 248 | 249 | /// Pending update to a window requested by the user. 250 | #[derive(Default, Debug, Clone, Copy)] 251 | pub struct SurfaceUserRequest { 252 | /// Whether `redraw` was requested. 253 | pub redraw_requested: bool, 254 | 255 | /// Wether the frame should be refreshed. 256 | pub refresh_frame: bool, 257 | } 258 | 259 | // The window update coming from the compositor. 260 | #[derive(Default, Debug, Clone)] 261 | pub struct SurfaceCompositorUpdate { 262 | /// New window configure. 263 | pub configure: Option, 264 | 265 | /// first 266 | pub first: bool, 267 | 268 | /// New scale factor. 269 | pub scale_factor: Option, 270 | 271 | /// Close the window. 272 | pub close_window: bool, 273 | } 274 | 275 | impl SctkEvent { 276 | pub fn to_native( 277 | self, 278 | modifiers: &mut Modifiers, 279 | surface_ids: &HashMap, 280 | destroyed_surface_ids: &HashMap, 281 | ) -> Vec { 282 | match self { 283 | // TODO Ashley: Platform specific multi-seat events? 284 | SctkEvent::SeatEvent { .. } => Default::default(), 285 | SctkEvent::PointerEvent { variant, .. } => match variant.kind { 286 | PointerEventKind::Enter { .. } => { 287 | vec![iced_native::Event::Mouse(mouse::Event::CursorEntered)] 288 | } 289 | PointerEventKind::Leave { .. } => { 290 | vec![iced_native::Event::Mouse(mouse::Event::CursorLeft)] 291 | } 292 | PointerEventKind::Motion { .. } => { 293 | vec![iced_native::Event::Mouse(mouse::Event::CursorMoved { 294 | position: Point::new(variant.position.0 as f32, variant.position.1 as f32), 295 | })] 296 | } 297 | PointerEventKind::Press { 298 | time: _, 299 | button, 300 | serial: _, 301 | } => pointer_button_to_native(button) 302 | .map(|b| iced_native::Event::Mouse(mouse::Event::ButtonPressed(b))) 303 | .into_iter() 304 | .collect(), // TODO Ashley: conversion 305 | PointerEventKind::Release { 306 | time: _, 307 | button, 308 | serial: _, 309 | } => pointer_button_to_native(button) 310 | .map(|b| iced_native::Event::Mouse(mouse::Event::ButtonReleased(b))) 311 | .into_iter() 312 | .collect(), // TODO Ashley: conversion 313 | PointerEventKind::Axis { 314 | time: _, 315 | horizontal, 316 | vertical, 317 | source, 318 | } => pointer_axis_to_native(source, horizontal, vertical) 319 | .map(|a| iced_native::Event::Mouse(mouse::Event::WheelScrolled { delta: a })) 320 | .into_iter() 321 | .collect(), // TODO Ashley: conversion 322 | }, 323 | SctkEvent::KeyboardEvent { 324 | variant, 325 | kbd_id: _, 326 | seat_id: _, 327 | } => match variant { 328 | KeyboardEventVariant::Leave(surface) => surface_ids 329 | .get(&surface.id()) 330 | .map(|id| match id { 331 | SurfaceIdWrapper::LayerSurface(_id) => { 332 | iced_native::Event::PlatformSpecific(PlatformSpecific::Wayland( 333 | wayland::Event::Layer(LayerEvent::Unfocused, surface, id.inner()), 334 | )) 335 | } 336 | SurfaceIdWrapper::Window(id) => { 337 | iced_native::Event::Window(*id, window::Event::Unfocused) 338 | } 339 | SurfaceIdWrapper::Popup(_id) => { 340 | iced_native::Event::PlatformSpecific(PlatformSpecific::Wayland( 341 | wayland::Event::Popup(PopupEvent::Unfocused, surface, id.inner()), 342 | )) 343 | } 344 | }) 345 | .into_iter() 346 | .collect(), 347 | KeyboardEventVariant::Enter(surface) => surface_ids 348 | .get(&surface.id()) 349 | .map(|id| match id { 350 | SurfaceIdWrapper::LayerSurface(_id) => { 351 | iced_native::Event::PlatformSpecific(PlatformSpecific::Wayland( 352 | wayland::Event::Layer(LayerEvent::Focused, surface, id.inner()), 353 | )) 354 | } 355 | SurfaceIdWrapper::Window(id) => { 356 | iced_native::Event::Window(*id, window::Event::Focused) 357 | } 358 | SurfaceIdWrapper::Popup(_id) => { 359 | iced_native::Event::PlatformSpecific(PlatformSpecific::Wayland( 360 | wayland::Event::Popup(PopupEvent::Focused, surface, id.inner()), 361 | )) 362 | } 363 | }) 364 | .into_iter() 365 | .collect(), 366 | KeyboardEventVariant::Press(ke) => { 367 | let mut skip_char = false; 368 | 369 | let mut events: Vec<_> = keysym_to_vkey(ke.keysym) 370 | .map(|k| { 371 | if k == KeyCode::Backspace { 372 | skip_char = true; 373 | } 374 | iced_native::Event::Keyboard(keyboard::Event::KeyPressed { 375 | key_code: k, 376 | modifiers: modifiers_to_native(*modifiers), 377 | }) 378 | }) 379 | .into_iter() 380 | .collect(); 381 | if !skip_char { 382 | if let Some(s) = ke.utf8 { 383 | let mut chars = s 384 | .chars() 385 | .map(|c| { 386 | iced_native::Event::Keyboard( 387 | keyboard::Event::CharacterReceived(c), 388 | ) 389 | }) 390 | .collect(); 391 | events.append(&mut chars); 392 | } 393 | } 394 | events 395 | } 396 | KeyboardEventVariant::Repeat(ke) => { 397 | let mut skip_char = false; 398 | 399 | let mut events: Vec<_> = keysym_to_vkey(ke.keysym) 400 | .map(|k| { 401 | if k == KeyCode::Backspace { 402 | skip_char = true; 403 | } 404 | iced_native::Event::Keyboard(keyboard::Event::KeyPressed { 405 | key_code: k, 406 | modifiers: modifiers_to_native(*modifiers), 407 | }) 408 | }) 409 | .into_iter() 410 | .collect(); 411 | if !skip_char { 412 | if let Some(s) = ke.utf8 { 413 | let mut chars = s 414 | .chars() 415 | .map(|c| { 416 | iced_native::Event::Keyboard( 417 | keyboard::Event::CharacterReceived(c), 418 | ) 419 | }) 420 | .collect(); 421 | events.append(&mut chars); 422 | } 423 | } 424 | events 425 | } 426 | KeyboardEventVariant::Release(k) => keysym_to_vkey(k.keysym) 427 | .map(|k| { 428 | iced_native::Event::Keyboard(keyboard::Event::KeyReleased { 429 | key_code: k, 430 | modifiers: modifiers_to_native(*modifiers), 431 | }) 432 | }) 433 | .into_iter() 434 | .collect(), 435 | KeyboardEventVariant::Modifiers(new_mods) => { 436 | *modifiers = new_mods; 437 | vec![iced_native::Event::Keyboard( 438 | keyboard::Event::ModifiersChanged(modifiers_to_native(new_mods)), 439 | )] 440 | } 441 | }, 442 | SctkEvent::WindowEvent { variant, id: surface } => match variant { 443 | // TODO Ashley: platform specific events for window 444 | WindowEventVariant::Created(..) => Default::default(), 445 | WindowEventVariant::Close => destroyed_surface_ids 446 | .get(&surface.id()) 447 | .map(|id| iced_native::Event::Window(id.inner(), window::Event::CloseRequested)) 448 | .into_iter() 449 | .collect(), 450 | WindowEventVariant::WmCapabilities(_) => Default::default(), 451 | WindowEventVariant::ConfigureBounds { .. } => Default::default(), 452 | WindowEventVariant::Configure(configure, surface, _) => { 453 | if configure.is_resizing() { 454 | let new_size = configure.new_size.unwrap(); 455 | surface_ids 456 | .get(&surface.id()) 457 | .map(|id| { 458 | iced_native::Event::Window( 459 | id.inner(), 460 | window::Event::Resized { 461 | width: new_size.0, 462 | height: new_size.1, 463 | }, 464 | ) 465 | }) 466 | .into_iter() 467 | .collect() 468 | } else { 469 | Default::default() 470 | } 471 | } 472 | }, 473 | SctkEvent::LayerSurfaceEvent { variant, id: surface } => match variant { 474 | LayerSurfaceEventVariant::Done => destroyed_surface_ids 475 | .get(&surface.id()) 476 | .map(|id| { 477 | iced_native::Event::PlatformSpecific(PlatformSpecific::Wayland( 478 | wayland::Event::Layer(LayerEvent::Done, surface, id.inner()), 479 | )) 480 | }) 481 | .into_iter() 482 | .collect(), 483 | _ => Default::default(), 484 | }, 485 | SctkEvent::PopupEvent { variant, id:surface, .. } => { 486 | match variant { 487 | PopupEventVariant::Done => destroyed_surface_ids 488 | .get(&surface.id()) 489 | .map(|id| { 490 | iced_native::Event::PlatformSpecific(PlatformSpecific::Wayland( 491 | wayland::Event::Popup(PopupEvent::Done, surface, id.inner()), 492 | )) 493 | }) 494 | .into_iter() 495 | .collect(), 496 | PopupEventVariant::Created(_, _) => Default::default(), // TODO 497 | PopupEventVariant::WmCapabilities(_) => Default::default(), // TODO 498 | PopupEventVariant::Configure(_, _, _) => Default::default(), // TODO 499 | PopupEventVariant::RepositionionedPopup { token } => Default::default(), // TODO 500 | } 501 | } 502 | SctkEvent::NewOutput { id, info } => Default::default(), 503 | SctkEvent::UpdateOutput { id, info } => Default::default(), 504 | SctkEvent::RemovedOutput(_) => Default::default(), 505 | SctkEvent::Draw(_) => Default::default(), 506 | SctkEvent::ScaleFactorChanged { 507 | factor, 508 | id, 509 | inner_size, 510 | } => Default::default(), 511 | } 512 | } 513 | } 514 | -------------------------------------------------------------------------------- /src/settings.rs: -------------------------------------------------------------------------------- 1 | use iced_native::command::platform_specific::wayland::{ 2 | layer_surface::SctkLayerSurfaceSettings, window::SctkWindowSettings, 3 | }; 4 | 5 | #[derive(Debug)] 6 | pub struct Settings { 7 | /// The data needed to initialize an [`Application`]. 8 | /// 9 | /// [`Application`]: crate::Application 10 | pub flags: Flags, 11 | /// optional keyboard repetition config 12 | pub kbd_repeat: Option, 13 | /// optional name and size of a custom pointer theme 14 | pub ptr_theme: Option<(String, u32)>, 15 | /// surface 16 | pub surface: InitialSurface, 17 | /// whether the application should exit on close of all windows 18 | pub exit_on_close_request: bool, 19 | } 20 | 21 | #[derive(Debug, Clone)] 22 | pub enum InitialSurface { 23 | LayerSurface(SctkLayerSurfaceSettings), 24 | XdgWindow(SctkWindowSettings), 25 | } 26 | 27 | impl Default for InitialSurface { 28 | fn default() -> Self { 29 | Self::LayerSurface(SctkLayerSurfaceSettings::default()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | /// The behavior of cursor grabbing. 2 | /// 3 | /// Use this enum with [`Window::set_cursor_grab`] to grab the cursor. 4 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 5 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 6 | pub enum CursorGrabMode { 7 | /// No grabbing of the cursor is performed. 8 | None, 9 | 10 | /// The cursor is confined to the window area. 11 | /// 12 | /// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you 13 | /// want to do so. 14 | /// 15 | /// ## Platform-specific 16 | /// 17 | /// - **macOS:** Not implemented. Always returns [`ExternalError::NotSupported`] for now. 18 | /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. 19 | Confined, 20 | 21 | /// The cursor is locked inside the window area to the certain position. 22 | /// 23 | /// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you 24 | /// want to do so. 25 | /// 26 | /// ## Platform-specific 27 | /// 28 | /// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for now. 29 | /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. 30 | Locked, 31 | } 32 | 33 | /// Describes the appearance of the mouse cursor. 34 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 35 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 36 | pub enum CursorIcon { 37 | /// The platform-dependent default cursor. 38 | Default, 39 | /// A simple crosshair. 40 | Crosshair, 41 | /// A hand (often used to indicate links in web browsers). 42 | Hand, 43 | /// Self explanatory. 44 | Arrow, 45 | /// Indicates something is to be moved. 46 | Move, 47 | /// Indicates text that may be selected or edited. 48 | Text, 49 | /// Program busy indicator. 50 | Wait, 51 | /// Help indicator (often rendered as a "?") 52 | Help, 53 | /// Progress indicator. Shows that processing is being done. But in contrast 54 | /// with "Wait" the user may still interact with the program. Often rendered 55 | /// as a spinning beach ball, or an arrow with a watch or hourglass. 56 | Progress, 57 | 58 | /// Cursor showing that something cannot be done. 59 | NotAllowed, 60 | ContextMenu, 61 | Cell, 62 | VerticalText, 63 | Alias, 64 | Copy, 65 | NoDrop, 66 | /// Indicates something can be grabbed. 67 | Grab, 68 | /// Indicates something is grabbed. 69 | Grabbing, 70 | AllScroll, 71 | ZoomIn, 72 | ZoomOut, 73 | 74 | /// Indicate that some edge is to be moved. For example, the 'SeResize' cursor 75 | /// is used when the movement starts from the south-east corner of the box. 76 | EResize, 77 | NResize, 78 | NeResize, 79 | NwResize, 80 | SResize, 81 | SeResize, 82 | SwResize, 83 | WResize, 84 | EwResize, 85 | NsResize, 86 | NeswResize, 87 | NwseResize, 88 | ColResize, 89 | RowResize, 90 | } 91 | 92 | impl Default for CursorIcon { 93 | fn default() -> Self { 94 | CursorIcon::Default 95 | } 96 | } 97 | 98 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 99 | pub enum Theme { 100 | Light, 101 | Dark, 102 | } 103 | 104 | /// ## Platform-specific 105 | /// 106 | /// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and [`Informational`]. 107 | /// 108 | /// [`Critical`]: Self::Critical 109 | /// [`Informational`]: Self::Informational 110 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 111 | pub enum UserAttentionType { 112 | /// ## Platform-specific 113 | /// 114 | /// - **macOS:** Bounces the dock icon until the application is in focus. 115 | /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus. 116 | Critical, 117 | /// ## Platform-specific 118 | /// 119 | /// - **macOS:** Bounces the dock icon once. 120 | /// - **Windows:** Flashes the taskbar button until the application is in focus. 121 | Informational, 122 | } 123 | 124 | impl Default for UserAttentionType { 125 | fn default() -> Self { 126 | UserAttentionType::Informational 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/widget.rs: -------------------------------------------------------------------------------- 1 | //! Display information and interactive controls in your application. 2 | pub use iced_native::widget::helpers::*; 3 | 4 | pub use iced_native::{column, row}; 5 | 6 | /// A container that distributes its contents vertically. 7 | pub type Column<'a, Message, Renderer = crate::Renderer> = 8 | iced_native::widget::Column<'a, Message, Renderer>; 9 | 10 | /// A container that distributes its contents horizontally. 11 | pub type Row<'a, Message, Renderer = crate::Renderer> = 12 | iced_native::widget::Row<'a, Message, Renderer>; 13 | 14 | pub mod text { 15 | //! Write some text for your users to read. 16 | pub use iced_native::widget::text::{Appearance, StyleSheet}; 17 | 18 | /// A paragraph of text. 19 | pub type Text<'a, Renderer = crate::Renderer> = 20 | iced_native::widget::Text<'a, Renderer>; 21 | } 22 | 23 | pub mod button { 24 | //! Allow your users to perform actions by pressing a button. 25 | pub use iced_native::widget::button::{Appearance, StyleSheet}; 26 | 27 | /// A widget that produces a message when clicked. 28 | pub type Button<'a, Message, Renderer = crate::Renderer> = 29 | iced_native::widget::Button<'a, Message, Renderer>; 30 | } 31 | 32 | pub mod checkbox { 33 | //! Show toggle controls using checkboxes. 34 | pub use iced_native::widget::checkbox::{Appearance, StyleSheet}; 35 | 36 | /// A box that can be checked. 37 | pub type Checkbox<'a, Message, Renderer = crate::Renderer> = 38 | iced_native::widget::Checkbox<'a, Message, Renderer>; 39 | } 40 | 41 | pub mod container { 42 | //! Decorate content and apply alignment. 43 | pub use iced_native::widget::container::{Appearance, StyleSheet}; 44 | 45 | /// An element decorating some content. 46 | pub type Container<'a, Message, Renderer = crate::Renderer> = 47 | iced_native::widget::Container<'a, Message, Renderer>; 48 | } 49 | 50 | pub mod pane_grid { 51 | //! Let your users split regions of your application and organize layout dynamically. 52 | //! 53 | //! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) 54 | //! 55 | //! # Example 56 | //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, 57 | //! drag and drop, and hotkey support. 58 | //! 59 | //! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.4/examples/pane_grid 60 | pub use iced_native::widget::pane_grid::{ 61 | Axis, Configuration, Direction, DragEvent, Line, Node, Pane, 62 | ResizeEvent, Split, State, StyleSheet, 63 | }; 64 | 65 | /// A collection of panes distributed using either vertical or horizontal splits 66 | /// to completely fill the space available. 67 | /// 68 | /// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) 69 | pub type PaneGrid<'a, Message, Renderer = crate::Renderer> = 70 | iced_native::widget::PaneGrid<'a, Message, Renderer>; 71 | 72 | /// The content of a [`Pane`]. 73 | pub type Content<'a, Message, Renderer = crate::Renderer> = 74 | iced_native::widget::pane_grid::Content<'a, Message, Renderer>; 75 | 76 | /// The title bar of a [`Pane`]. 77 | pub type TitleBar<'a, Message, Renderer = crate::Renderer> = 78 | iced_native::widget::pane_grid::TitleBar<'a, Message, Renderer>; 79 | } 80 | 81 | pub mod pick_list { 82 | //! Display a dropdown list of selectable values. 83 | pub use iced_native::widget::pick_list::{Appearance, StyleSheet}; 84 | 85 | /// A widget allowing the selection of a single value from a list of options. 86 | pub type PickList<'a, T, Message, Renderer = crate::Renderer> = 87 | iced_native::widget::PickList<'a, T, Message, Renderer>; 88 | } 89 | 90 | pub mod radio { 91 | //! Create choices using radio buttons. 92 | pub use iced_native::widget::radio::{Appearance, StyleSheet}; 93 | 94 | /// A circular button representing a choice. 95 | pub type Radio = 96 | iced_native::widget::Radio; 97 | } 98 | 99 | pub mod scrollable { 100 | //! Navigate an endless amount of content with a scrollbar. 101 | pub use iced_native::widget::scrollable::{ 102 | snap_to, style::Scrollbar, style::Scroller, Id, StyleSheet, 103 | }; 104 | 105 | /// A widget that can vertically display an infinite amount of content 106 | /// with a scrollbar. 107 | pub type Scrollable<'a, Message, Renderer = crate::Renderer> = 108 | iced_native::widget::Scrollable<'a, Message, Renderer>; 109 | } 110 | 111 | pub mod toggler { 112 | //! Show toggle controls using togglers. 113 | pub use iced_native::widget::toggler::{Appearance, StyleSheet}; 114 | 115 | /// A toggler widget. 116 | pub type Toggler<'a, Message, Renderer = crate::Renderer> = 117 | iced_native::widget::Toggler<'a, Message, Renderer>; 118 | } 119 | 120 | pub mod text_input { 121 | //! Display fields that can be filled with text. 122 | pub use iced_native::widget::text_input::{ 123 | focus, Appearance, Id, StyleSheet, 124 | }; 125 | 126 | /// A field that can be filled with text. 127 | pub type TextInput<'a, Message, Renderer = crate::Renderer> = 128 | iced_native::widget::TextInput<'a, Message, Renderer>; 129 | } 130 | 131 | pub mod tooltip { 132 | //! Display a widget over another. 133 | pub use iced_native::widget::tooltip::Position; 134 | 135 | /// A widget allowing the selection of a single value from a list of options. 136 | pub type Tooltip<'a, Message, Renderer = crate::Renderer> = 137 | iced_native::widget::Tooltip<'a, Message, Renderer>; 138 | } 139 | 140 | pub use iced_native::widget::progress_bar; 141 | pub use iced_native::widget::rule; 142 | pub use iced_native::widget::slider; 143 | pub use iced_native::widget::Space; 144 | 145 | pub use button::Button; 146 | pub use checkbox::Checkbox; 147 | pub use container::Container; 148 | pub use pane_grid::PaneGrid; 149 | pub use pick_list::PickList; 150 | pub use progress_bar::ProgressBar; 151 | pub use radio::Radio; 152 | pub use rule::Rule; 153 | pub use scrollable::Scrollable; 154 | pub use slider::Slider; 155 | pub use text::Text; 156 | pub use text_input::TextInput; 157 | pub use toggler::Toggler; 158 | pub use tooltip::Tooltip; 159 | 160 | #[cfg(feature = "canvas")] 161 | #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] 162 | pub use iced_graphics::widget::canvas; 163 | 164 | #[cfg(feature = "canvas")] 165 | #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] 166 | /// Creates a new [`Canvas`]. 167 | pub fn canvas(program: P) -> Canvas 168 | where 169 | P: canvas::Program, 170 | { 171 | Canvas::new(program) 172 | } 173 | 174 | #[cfg(feature = "image")] 175 | #[cfg_attr(docsrs, doc(cfg(feature = "image")))] 176 | pub mod image { 177 | //! Display images in your user interface. 178 | pub use iced_native::image::Handle; 179 | 180 | /// A frame that displays an image. 181 | pub type Image = iced_native::widget::Image; 182 | 183 | pub use iced_native::widget::image::viewer; 184 | pub use viewer::Viewer; 185 | } 186 | 187 | #[cfg(feature = "qr_code")] 188 | #[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] 189 | pub use iced_graphics::widget::qr_code; 190 | 191 | #[cfg(feature = "svg")] 192 | #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] 193 | pub mod svg { 194 | //! Display vector graphics in your application. 195 | pub use iced_native::svg::Handle; 196 | pub use iced_native::widget::Svg; 197 | } 198 | 199 | #[cfg(feature = "canvas")] 200 | #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] 201 | pub use canvas::Canvas; 202 | 203 | #[cfg(feature = "image")] 204 | #[cfg_attr(docsrs, doc(cfg(feature = "image")))] 205 | pub use image::Image; 206 | 207 | #[cfg(feature = "qr_code")] 208 | #[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] 209 | pub use qr_code::QRCode; 210 | 211 | #[cfg(feature = "svg")] 212 | #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] 213 | pub use svg::Svg; 214 | 215 | use crate::Command; 216 | use iced_native::widget::operation; 217 | 218 | /// Focuses the previous focusable widget. 219 | pub fn focus_previous() -> Command 220 | where 221 | Message: 'static, 222 | { 223 | Command::widget(operation::focusable::focus_previous()) 224 | } 225 | 226 | /// Focuses the next focusable widget. 227 | pub fn focus_next() -> Command 228 | where 229 | Message: 'static, 230 | { 231 | Command::widget(operation::focusable::focus_next()) 232 | } 233 | -------------------------------------------------------------------------------- /src/window.rs: -------------------------------------------------------------------------------- 1 | pub fn resize() { 2 | todo!() 3 | } 4 | --------------------------------------------------------------------------------