Skip to content

Conversation

@WShad
Copy link
Contributor

@WShad WShad commented Jul 19, 2025

Adds language widget displaying current keyboard layout language.
2025-07-20_00:47:10

@WShad WShad marked this pull request as ready for review July 19, 2025 21:33
@WShad
Copy link
Contributor Author

WShad commented Jul 29, 2025

Implemented IPC-client + language widget based on it

@WShad
Copy link
Contributor Author

WShad commented Aug 1, 2025

@ammen99, better?))

@NamorNiradnug
Copy link
Collaborator

NamorNiradnug commented Aug 1, 2025

Hi! I haven't checked the PR carefully yet but I suppose it should use yyjson instead of nlohmann_json because wayfire uses the former. Could you please fix that?

UPD: also please fix the formatting.

int inotify_css_fd;
wf::config::config_manager_t config;
zwf_shell_manager_v2 *wf_shell_manager = nullptr;
WayfireIPC *ipc = nullptr;
Copy link
Collaborator

@NamorNiradnug NamorNiradnug Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest making WayfireIPC a singleton so that it won't be initialized until needed.

I usually do it like this:

  • WayfireIPC doesn't have a public ctor
  • has private static std::weak_ptr<WayfireIPC> instance_
  • has public static std::shared_ptr<WayfireIPC> Instance() which simply returns instance_.lock()
  • has public static std::shared_ptr<WayfireIPC> Open() (or something, maybe you will come up with a better name) which initialize the instance_ if it's expired

Instance() should be called in reload_widgets so that we don't close-and-open the socket on every reload.

Probably Lock() would be a better name for Instance(), but I would still call it Instance for consistency with existing singletons.

See https://github.com/WayfireWM/wf-shell/blob/master/src/panel/widgets/tray/watcher.hpp#L18-L31 and https://github.com/WayfireWM/wf-shell/blob/master/src/panel/panel.cpp#L268.


class WayfireIPC
{
int socket_fd;
Copy link
Collaborator

@NamorNiradnug NamorNiradnug Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered using Gio's SocketClient instead of handling it manually? I believe it would simplify the code.

@ammen99
Copy link
Member

ammen99 commented Nov 4, 2025

@WShad sorry for the delay, we were finishing the gtk4 port .. if you still have interest, please update this PR to gtk4 as well.

@ammen99
Copy link
Member

ammen99 commented Nov 14, 2025

@WShad thanks for your updates! I looked briefly over your PR, a few things that stood out to me:

  • uncrustify.ini was copied from Wayfire's repo, but I don't think we use it here? So it shouldnt' be added
  • You have copied the yyjson wrapper from Wayfire. I find it ok to use either nlohmann/json or yyjson here, though I suppose yyjson makes us closer to Wayfire. In any case, if we are going to use wrapped yyjson like wayfire, we shouldn't duplicate the code. If you want I can make a new repo here where we can put the json wrapper and then we can share it between wayfire and wf-shell as a subproject.

@WShad
Copy link
Contributor Author

WShad commented Nov 14, 2025

Wow! Thanks for so fast feedback!)))

  1. Removed
  2. As you say. Lead me, plz)

@ammen99
Copy link
Member

ammen99 commented Nov 14, 2025

I will separate wayfire's json handling into a subproject and will let you know when it is ready for incorporating into your pr :)

@ammen99
Copy link
Member

ammen99 commented Nov 14, 2025

@WShad It was actually easier than I expected, here is the json wrapper itself: https://github.com/WayfireWM/wf-json

Here is the Wayfire PR where it is used WayfireWM/wayfire#2892
You can add it as a subproject to wf-shell as well and then you get the json wrapper header automatically. The rest of the IPC handling is glib-specific so maybe best to keep it here in wf-shell.

button.show();

ipc->subscribe(this, {"keyboard-modifier-state-changed"});
ipc->send("{\"method\":\"wayfire/get-keyboard-state\"}", [=] (json_t data)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find your design very interesting with this send and the callback for the response. One thing I am not very sure about however is this, what deregisters the callback if the WayfireLanguage class is destroyed while waiting for the reply?

Copy link
Contributor Author

@WShad WShad Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find your design very interesting with this send and the callback for the response

Thanks! I'm glad to hear that)

what deregisters the callback if the WayfireLanguage class is destroyed while waiting for the reply?

Nice point. Didn't think about it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One idea would be to somehow connect the full ipc->send() version with the IPC subscriber interface, then have the IPC subscriber interface deregister itself from the IPC socket automatically. This is what we do for signals in Wayfire itself. Maybe the implementation will give you some inspirations, but feel free to come up with a different idea if you have a better one :)

https://github.com/WayfireWM/wayfire/blob/master/src/api/wayfire/signal-provider.hpp
https://github.com/WayfireWM/wayfire/blob/8a3e1cae5beda079060d7c5725812bdc589a6ae3/src/core/object.cpp#L7-L59

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved. Now widgets must have their own IPC clients, they deregister themselves and all replies to that clients will be safely ignored.

Copy link
Member

@ammen99 ammen99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getting closer to perfection :) a few smaller items that i noticed, otherwise lgtm

WayfireIPC::~WayfireIPC()
{
disconnect();
LOGI("IPC destroyed");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think we need this message in 'production' :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disagree, but - ok


while (connection->get_socket()->get_available_bytes() > 0)
{
received = input->read(&length, 4);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to have length as a local variable, it is not some 'state' which needs to be preserved across method calls.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also instead of 4, consider using sizeof(length), that way I think it is way clearer why we read those bytes separately :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to have length as a local variable, it is not some 'state' which needs to be preserved across method calls.

Will fix. Legacy of prev impl)

received = input->read(&length, 4);
if (received == -1)
{
LOGE("Receive message length failed");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when we log errors, it would make sense to also indicate where they are coming from, for example: "Error in Wayfire IPC communication: failed to receive message length"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(also applies to all the other LOGE calls in this file / PR


while (connection->get_socket()->get_available_bytes() > 0)
{
received = input->read(&length, 4);
Copy link
Member

@ammen99 ammen99 Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing: when calling read(), I we are not guaranteed to get the full number of bytes we request. In particular, if we have a longer ipc message, I think read could stop in the middle, return just some part of the message. At least the underlying read() syscall works this way. Example of what Wayfire does to combat this: https://github.com/WayfireWM/wayfire/blob/9c427024e849b0c676184c1af3c82b38f1f8fb67/plugins/ipc/ipc.cpp#L171-L285

Copy link
Contributor Author

@WShad WShad Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't actually confirm that. I experimented a lot and this is a results:

  • under the pressure of data socket can get buffer overflow, if not read frequent enough
  • if n bites written - n bites can be read (not like in a tcp connection)
  • if server lags on read or write - client disconnected immediately

Can't reproduce situation where was read less than was written.
Using this IPC for three months, fixed some bugs, have no problems.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • under the pressure of data socket can get buffer overflow, if not read frequent enough

That's exactly the issue! It can be that the buffer can only accommodate the message partially.

I mean in practice I expect this to not cause any issues, so maybe we could simply leave this as a TODO comment in the code and fix it when somebody actually complains.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case would be good to at least check what is going on, so instead of errorring on n_bytes_read == 0, we need to error on n_bytes_read != n_bytes_wanted

@ammen99
Copy link
Member

ammen99 commented Nov 29, 2025

@WShad For the CI to work, I think you need to add cmake to the list of dependencies, or to install yyjson as a dependency so that we don't have to build it as a submodule. Also you'll need to fix the codestyle. Otherwise LGTM, good work!

@WShad
Copy link
Contributor Author

WShad commented Dec 7, 2025

@WShad
Copy link
Contributor Author

WShad commented Dec 7, 2025

I don't understand... How? My action completed successfully, they are the same - how it even can be?

@ammen99
Copy link
Member

ammen99 commented Dec 8, 2025

@WShad I also find it hard to comprehend why it works on the one CI and not on the other, but my guess would be that you add the json submodule as a dependency only to the util static library. But it should also be a dependency of the panel itself (because it needs the json include headers). To solve such issues, we have a dependency object which includes all the necessary stuff, you can add json as a dependency to it as well:

libutil = declare_dependency(link_with: util, include_directories: util_includes)

I believe this will fix the CI issue.

A note on uncrustify: we use a custom uncrustify to be consistent with Wayfire. Wayfire itself uses a custom uncrustify because uncrustify-git currently crashesh on Wayfire's codebase, plus it had some formatting bugs which I fixed in my fork ... At least however, the CI will give you a patch to apply so you don't have to set it up locally, see https://github.com/WayfireWM/wf-shell/actions/runs/20010484621/job/57379300256?pr=301#step:9:1

@WShad
Copy link
Contributor Author

WShad commented Dec 8, 2025

my guess would be that you add the json submodule as a dependency only to the util static library. But it should also be a dependency of the panel itself (because it needs the json include headers)

Exactly! And why i lost that dependency(?) - it compiles locally just fine, without explicitly included 'json'. Strange...

A note on uncrustify

Got it

Will compile now.

Copy link
Member

@ammen99 ammen99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM now, thanks a lot!

@ammen99 ammen99 merged commit d935c4b into WayfireWM:master Dec 8, 2025
2 checks passed
@birdie-github
Copy link

Does it support a language emoji? If not, would be very nice to have along with an option to either show:

  • text only (EN)
  • text and language emoji (EN 🇬🇧)
  • just language emoji (🇬🇧)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants